Module: PartitionGardener

Defined in:
lib/partition_gardener.rb,
lib/partition_gardener/cli.rb,
lib/partition_gardener/plan.rb,
lib/partition_gardener/audit.rb,
lib/partition_gardener/blank.rb,
lib/partition_gardener/rails.rb,
lib/partition_gardener/naming.rb,
lib/partition_gardener/planner.rb,
lib/partition_gardener/version.rb,
lib/partition_gardener/executor.rb,
lib/partition_gardener/registry.rb,
lib/partition_gardener/strategy.rb,
lib/partition_gardener/plan_diff.rb,
lib/partition_gardener/predicate.rb,
lib/partition_gardener/templates.rb,
lib/partition_gardener/connection.rb,
lib/partition_gardener/run_failed.rb,
lib/partition_gardener/run_record.rb,
lib/partition_gardener/date_bucket.rb,
lib/partition_gardener/plan_report.rb,
lib/partition_gardener/run_metrics.rb,
lib/partition_gardener/hash_routing.rb,
lib/partition_gardener/plan_applier.rb,
lib/partition_gardener/advisory_lock.rb,
lib/partition_gardener/configuration.rb,
lib/partition_gardener/date_calendar.rb,
lib/partition_gardener/gap_detection.rb,
lib/partition_gardener/pg_connection.rb,
lib/partition_gardener/config_document.rb,
lib/partition_gardener/archive_retention.rb,
lib/partition_gardener/default_partition.rb,
lib/partition_gardener/layout/three_area.rb,
lib/partition_gardener/lock_not_acquired.rb,
lib/partition_gardener/stdlib_extensions.rb,
lib/partition_gardener/child_column_align.rb,
lib/partition_gardener/strategy/composite.rb,
lib/partition_gardener/maintenance_backend.rb,
lib/partition_gardener/strategy/date_range.rb,
lib/partition_gardener/strategy/list_split.rb,
lib/partition_gardener/layout/calendar_year.rb,
lib/partition_gardener/layout/zone_segments.rb,
lib/partition_gardener/sql_run_record_store.rb,
lib/partition_gardener/layout/integer_window.rb,
lib/partition_gardener/layout/sliding_window.rb,
lib/partition_gardener/date_range_maintenance.rb,
lib/partition_gardener/layout/occupied_window.rb,
lib/partition_gardener/missing_conflict_index.rb,
lib/partition_gardener/strategy/hash_branches.rb,
lib/partition_gardener/strategy/integer_range.rb,
lib/partition_gardener/unmoved_rows_remaining.rb,
lib/partition_gardener/memory_run_record_store.rb,
lib/partition_gardener/strategy/cursor_columns.rb,
lib/partition_gardener/orphaned_rebalance_staging.rb,
lib/partition_gardener/premake_monthly_maintenance.rb,
lib/partition_gardener/migration/hot_switch_concern.rb,
lib/partition_gardener/strategy/requires_default_partition.rb,
sig/partition_gardener.rbs

Defined Under Namespace

Modules: AdvisoryLock, Blank, ConfigDocument, Connection, DateBucket, DateCalendar, HashRouting, IntegerDurationMethods, Layout, MaintenanceBackend, Migration, Naming, Plan, Predicate, Registry, Strategy, Templates Classes: ArchiveRetention, Audit, CLI, ChildColumnAlign, CompositeMaintenance, Configuration, DateRangeMaintenance, DefaultPartition, Executor, GapDetection, LockNotAcquired, MemoryRunRecordStore, MissingConflictIndex, MissingDefaultPartition, OrphanedRebalanceStaging, PgConnection, PlanApplier, PlanDiff, PlanReport, Planner, PremakeMonthlyMaintenance, Railtie, RunFailed, RunMetrics, RunRecord, RunSummary, SqlRunRecordStore, UnmovedRowsRemaining

Constant Summary collapse

MOVE_BATCH_SIZE =

Returns:

  • (Integer)
10_000
FUTURE_MONTH_PARTITION_ROW_THRESHOLD =

Returns:

  • (Integer)
100_000
DEFAULT_STATEMENT_TIMEOUT =

Returns:

  • (ActiveSupport::Duration)
300
VERSION =

Returns:

  • (String)
"0.3.5"
ActiveRecordRunRecordStore =
SqlRunRecordStore
ThreeAreaMaintenance =
DateRangeMaintenance

Class Method Summary collapse

Class Method Details

.audit(table_name) ⇒ Object

Parameters:

  • table_name (String)

Returns:

  • (Object)

Raises:

  • (ArgumentError)


89
90
91
92
93
94
95
96
97
98
# File 'lib/partition_gardener.rb', line 89

def audit(table_name)
  configs = Registry.configs_for_table(table_name)
  raise ArgumentError, "no config for #{table_name}" if configs.empty?

  if configs.one?
    Audit.call(table_name, config: configs.first)
  else
    configs.map { |config| Audit.call(config[:table_name], config: config) }
  end
end

.configurationObject



91
92
93
# File 'lib/partition_gardener/configuration.rb', line 91

def configuration
  @configuration ||= Configuration.new
end

.configure {|configuration| ... } ⇒ Object

Yields:



95
96
97
98
# File 'lib/partition_gardener/configuration.rb', line 95

def configure
  yield(configuration) if block_given?
  configuration
end

.emit_run_metadata(metrics, job_class_name:) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
# File 'lib/partition_gardener.rb', line 128

def (metrics, job_class_name:)
  configuration.notify(
    "[PartitionGardener] Finished #{metrics.table_name}",
    context: {
      table_name: metrics.table_name,
      job: job_class_name,
      action: "run_metadata",
      run_metadata: metrics.to_h
    }
  )
end

.maintenance_for(config, job_class_name: "PartitionGardener") ⇒ Object

Parameters:

  • config (Hash[Symbol, untyped])
  • job_class_name: (String) (defaults to: "PartitionGardener")

Returns:

  • (Object)


117
118
119
120
121
122
123
124
125
126
# File 'lib/partition_gardener.rb', line 117

def maintenance_for(config, job_class_name: "PartitionGardener")
  case config.fetch(:layout)
  when :composite
    CompositeMaintenance.new(config, job_class_name: job_class_name)
  when :premake_monthly
    PremakeMonthlyMaintenance.new(config, job_class_name: job_class_name)
  else
    ThreeAreaMaintenance.new(config, job_class_name: job_class_name)
  end
end

.plan(table_name: nil, config: nil) ⇒ Object

Parameters:

  • table_name: (String, nil) (defaults to: nil)
  • config: (Hash[Symbol, untyped], nil) (defaults to: nil)

Returns:

  • (Object)

Raises:

  • (ArgumentError)


100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/partition_gardener.rb', line 100

def plan(table_name: nil, config: nil)
  if config
    return PlanReport.build(config)
  end

  raise ArgumentError, "table_name or config is required" unless table_name

  configs = Registry.configs_for_table(table_name)
  raise ArgumentError, "no config for #{table_name}" if configs.empty?

  if configs.one?
    PlanReport.build(configs.first)
  else
    configs.map { |entry| PlanReport.build(entry) }
  end
end

.plan_allObject



140
141
142
143
144
145
146
147
148
# File 'lib/partition_gardener.rb', line 140

def plan_all
  reports = []
  Registry.each_table_config do |config|
    next unless Connection.table_is_partitioned?(config[:table_name])

    reports << PlanReport.build(config).to_h
  end
  reports
end

.plan_all_tablesObject



150
151
152
# File 'lib/partition_gardener.rb', line 150

def plan_all_tables
  Registry.each_table_config.map { |config| PlanReport.build(config) }
end

.reset_configuration!Object



100
101
102
# File 'lib/partition_gardener/configuration.rb', line 100

def reset_configuration!
  @configuration = Configuration.new
end

.run!(statement_timeout: DEFAULT_STATEMENT_TIMEOUT, job_class_name: "PartitionGardener", continue_on_error: configuration.continue_on_error, dry_run: false, table_name: nil) ⇒ Object

Raises:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/partition_gardener.rb', line 65

def run!(statement_timeout: DEFAULT_STATEMENT_TIMEOUT, job_class_name: "PartitionGardener", continue_on_error: configuration.continue_on_error, dry_run: false, table_name: nil)
  return plan_all if dry_run

  errors = []
  table_results = []
  filter_table = table_name
  configs = filter_table ? Registry.configs_for_table(filter_table) : Registry.expanded_table_configs

  configs.each do |config|
    table_results << run_single_table!(
      config,
      statement_timeout: statement_timeout,
      job_class_name: job_class_name,
      continue_on_error: continue_on_error,
      errors: errors
    )
  end

  summary = RunSummary.new(tables: table_results, errors: errors)
  raise RunFailed, errors if errors.any?

  summary
end

.run_single_table!(config, statement_timeout:, job_class_name:, continue_on_error:, errors:) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/partition_gardener.rb', line 154

def run_single_table!(config, statement_timeout:, job_class_name:, continue_on_error:, errors:)
  table_name = config[:table_name]
  table_timeout = config.fetch(:statement_timeout, statement_timeout)
  metrics = RunMetrics.new(table_name)

  configuration.with_statement_timeout(table_timeout) do
    Connection.clear_attached_partitions_cache!

    unless Connection.table_is_partitioned?(table_name)
      metrics.mark_skipped!("not_partitioned")
      return metrics
    end

    if MaintenanceBackend.skipped?(config)
      metrics.mark_skipped!("maintenance_backend_pg_partman")
      return metrics
    end

    configuration.current_run_metrics = metrics

    AdvisoryLock.with_table_lock(table_name) do
      DefaultPartition.ensure!(config)
      maintenance_for(config, job_class_name: job_class_name).run!
    end

    metrics.finish!
    (metrics, job_class_name: job_class_name)
    metrics
  rescue LockNotAcquired => error
    metrics.mark_skipped!("lock_not_acquired")
    configuration.notify(
      "[PartitionGardener] skipped #{table_name}: #{error.message}",
      context: {
        table_name: table_name,
        job: job_class_name,
        action: "lock"
      }
    )
    metrics
  rescue => error
    metrics.finish!
    configuration.notify(
      error,
      context: {
        table_name: table_name,
        job: job_class_name,
        action: "run",
        run_metadata: metrics.to_h
      }
    )
    errors << error
    raise unless continue_on_error

    metrics
  ensure
    configuration.current_run_metrics = nil
  end
end