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/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/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, 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.0"
ActiveRecordRunRecordStore =
SqlRunRecordStore
ThreeAreaMaintenance =
DateRangeMaintenance

Class Method Summary collapse

Class Method Details

.audit(table_name) ⇒ Object

Parameters:

  • table_name (String)

Returns:

  • (Object)

Raises:

  • (ArgumentError)


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

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



89
90
91
# File 'lib/partition_gardener/configuration.rb', line 89

def configuration
  @configuration ||= Configuration.new
end

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

Yields:



93
94
95
96
# File 'lib/partition_gardener/configuration.rb', line 93

def configure
  yield(configuration) if block_given?
  configuration
end

.emit_run_metadata(metrics, job_class_name:) ⇒ Object



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

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)


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

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)


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

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



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

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



148
149
150
# File 'lib/partition_gardener.rb', line 148

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

.reset_configuration!Object



98
99
100
# File 'lib/partition_gardener/configuration.rb', line 98

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:



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

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



152
153
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
# File 'lib/partition_gardener.rb', line 152

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