online_migrations 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +34 -31
  4. data/docs/background_migrations.md +36 -4
  5. data/docs/configuring.md +3 -2
  6. data/lib/generators/online_migrations/install_generator.rb +3 -7
  7. data/lib/generators/online_migrations/templates/add_sharding_to_online_migrations.rb.tt +18 -0
  8. data/lib/generators/online_migrations/templates/initializer.rb.tt +5 -3
  9. data/lib/generators/online_migrations/templates/migration.rb.tt +8 -3
  10. data/lib/generators/online_migrations/upgrade_generator.rb +33 -0
  11. data/lib/online_migrations/background_migrations/application_record.rb +13 -0
  12. data/lib/online_migrations/background_migrations/backfill_column.rb +1 -1
  13. data/lib/online_migrations/background_migrations/copy_column.rb +6 -20
  14. data/lib/online_migrations/background_migrations/delete_orphaned_records.rb +2 -20
  15. data/lib/online_migrations/background_migrations/migration.rb +123 -34
  16. data/lib/online_migrations/background_migrations/migration_helpers.rb +0 -4
  17. data/lib/online_migrations/background_migrations/migration_job.rb +15 -12
  18. data/lib/online_migrations/background_migrations/migration_job_runner.rb +2 -2
  19. data/lib/online_migrations/background_migrations/migration_runner.rb +56 -11
  20. data/lib/online_migrations/background_migrations/reset_counters.rb +3 -9
  21. data/lib/online_migrations/background_migrations/scheduler.rb +5 -15
  22. data/lib/online_migrations/change_column_type_helpers.rb +68 -83
  23. data/lib/online_migrations/command_checker.rb +11 -29
  24. data/lib/online_migrations/config.rb +7 -15
  25. data/lib/online_migrations/copy_trigger.rb +15 -10
  26. data/lib/online_migrations/error_messages.rb +13 -25
  27. data/lib/online_migrations/foreign_keys_collector.rb +2 -2
  28. data/lib/online_migrations/indexes_collector.rb +3 -3
  29. data/lib/online_migrations/lock_retrier.rb +4 -9
  30. data/lib/online_migrations/schema_cache.rb +0 -6
  31. data/lib/online_migrations/schema_dumper.rb +1 -1
  32. data/lib/online_migrations/schema_statements.rb +64 -256
  33. data/lib/online_migrations/utils.rb +18 -56
  34. data/lib/online_migrations/verbose_sql_logs.rb +3 -2
  35. data/lib/online_migrations/version.rb +1 -1
  36. data/lib/online_migrations.rb +7 -6
  37. metadata +8 -7
  38. data/lib/online_migrations/background_migrations/advisory_lock.rb +0 -62
  39. data/lib/online_migrations/foreign_key_definition.rb +0 -17
@@ -2,7 +2,7 @@
2
2
 
3
3
  module OnlineMigrations
4
4
  module BackgroundMigrations
5
- class Migration < ActiveRecord::Base
5
+ class Migration < ApplicationRecord
6
6
  STATUSES = [
7
7
  :enqueued, # The migration has been enqueued by the user.
8
8
  :running, # The migration is being performed by a migration executor.
@@ -15,25 +15,29 @@ module OnlineMigrations
15
15
  self.table_name = :background_migrations
16
16
 
17
17
  scope :queue_order, -> { order(created_at: :asc) }
18
+ scope :runnable, -> { where(composite: false) }
18
19
  scope :active, -> { where(status: [statuses[:enqueued], statuses[:running]]) }
20
+ scope :except_succeeded, -> { where.not(status: :succeeded) }
19
21
  scope :for_migration_name, ->(migration_name) { where(migration_name: normalize_migration_name(migration_name)) }
20
22
  scope :for_configuration, ->(migration_name, arguments) do
21
23
  for_migration_name(migration_name).where("arguments = ?", arguments.to_json)
22
24
  end
23
25
 
24
- enum status: STATUSES.map { |status| [status, status.to_s] }.to_h
26
+ enum status: STATUSES.index_with(&:to_s)
25
27
 
28
+ belongs_to :parent, class_name: name, optional: true
29
+ has_many :children, class_name: name, foreign_key: :parent_id
26
30
  has_many :migration_jobs
27
31
 
28
32
  validates :migration_name, :batch_column_name, presence: true
29
33
 
30
- validates :min_value, :max_value, :batch_size, :sub_batch_size,
31
- presence: true, numericality: { greater_than: 0 }
34
+ validates :batch_size, :sub_batch_size, presence: true, numericality: { greater_than: 0 }
35
+ validates :min_value, :max_value, presence: true, numericality: { greater_than: 0, unless: :composite? }
32
36
 
33
37
  validates :batch_pause, :sub_batch_pause_ms, presence: true,
34
38
  numericality: { greater_than_or_equal_to: 0 }
35
- validates :rows_count, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
36
- validates :arguments, uniqueness: { scope: :migration_name }
39
+ validates :rows_count, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true, unless: :composite?
40
+ validates :arguments, uniqueness: { scope: [:migration_name, :shard] }
37
41
 
38
42
  validate :validate_batch_column_values
39
43
  validate :validate_batch_sizes
@@ -43,6 +47,8 @@ module OnlineMigrations
43
47
  validates_with MigrationStatusValidator, on: :update
44
48
 
45
49
  before_validation :set_defaults
50
+ before_create :create_child_migrations, if: :composite?
51
+ before_update :copy_attributes_to_children, if: :composite?
46
52
 
47
53
  # @private
48
54
  def self.normalize_migration_name(migration_name)
@@ -58,28 +64,53 @@ module OnlineMigrations
58
64
  succeeded? || failed?
59
65
  end
60
66
 
67
+ # Overwrite enum's generated method to correctly work for composite migrations.
68
+ def paused!
69
+ return super if !composite?
70
+
71
+ transaction do
72
+ super
73
+ children.each { |child| child.paused! if child.enqueued? || child.running? }
74
+ end
75
+ end
76
+
77
+ # Overwrite enum's generated method to correctly work for composite migrations.
78
+ def running!
79
+ return super if !composite?
80
+
81
+ transaction do
82
+ super
83
+ children.each { |child| child.running! if child.paused? }
84
+ end
85
+ end
86
+
61
87
  def last_job
62
- migration_jobs.order(max_value: :desc).first
88
+ migration_jobs.order(:max_value).last
63
89
  end
64
90
 
65
91
  def last_completed_job
66
- migration_jobs.completed.order(finished_at: :desc).first
92
+ migration_jobs.completed.order(:finished_at).last
67
93
  end
68
94
 
69
95
  # Returns the progress of the background migration.
70
96
  #
71
97
  # @return [Float, nil]
72
- # - when background migration is configured to not to track progress, returns `nil`
73
- # - otherwise returns value in range of 0.0 and 1.0
98
+ # - when background migration is configured to not track progress, returns `nil`
99
+ # - otherwise returns value in range from 0.0 to 100.0
74
100
  #
75
101
  def progress
76
102
  if succeeded?
77
- 1.0
103
+ 100.0
104
+ elsif composite?
105
+ progresses = children.map(&:progress).compact
106
+ if progresses.any?
107
+ (progresses.sum / progresses.size).round(2)
108
+ end
78
109
  elsif rows_count
79
110
  jobs_rows_count = migration_jobs.succeeded.sum(:batch_size)
80
111
  # The last migration job may need to process the amount of rows
81
112
  # less than the batch size, so we can get a value > 1.0.
82
- [jobs_rows_count.to_f / rows_count, 1.0].min
113
+ ([jobs_rows_count.to_f / rows_count, 1.0].min * 100).round(2)
83
114
  end
84
115
  end
85
116
 
@@ -95,13 +126,19 @@ module OnlineMigrations
95
126
  migration_object.relation
96
127
  end
97
128
 
129
+ def migration_model
130
+ migration_relation.model
131
+ end
132
+
98
133
  # Returns whether the interval between previous step run has passed.
99
134
  # @return [Boolean]
100
135
  #
101
136
  def interval_elapsed?
102
- if migration_jobs.running.exists?
137
+ last_active_job = migration_jobs.active.order(:updated_at).last
138
+
139
+ if last_active_job && !last_active_job.stuck?
103
140
  false
104
- elsif (job = last_completed_job)
141
+ elsif batch_pause > 0 && (job = last_completed_job)
105
142
  job.finished_at + batch_pause <= Time.current
106
143
  else
107
144
  true
@@ -123,6 +160,14 @@ module OnlineMigrations
123
160
  end
124
161
  end
125
162
 
163
+ # @private
164
+ def on_shard(&block)
165
+ abstract_class = find_abstract_class(migration_model)
166
+
167
+ shard = (self.shard || abstract_class.default_shard).to_sym
168
+ abstract_class.connected_to(shard: shard, role: :writing, &block)
169
+ end
170
+
126
171
  # @private
127
172
  def reset_failed_jobs_attempts
128
173
  iterator = BatchIterator.new(migration_jobs.failed.attempts_exceeded)
@@ -138,16 +183,10 @@ module OnlineMigrations
138
183
 
139
184
  # rubocop:disable Lint/UnreachableLoop
140
185
  iterator.each_batch(of: batch_size, column: batch_column_name, start: next_min_value) do |relation|
141
- if Utils.ar_version <= 4.2
142
- # Active Record <= 4.2 does not support pluck with Arel nodes
143
- quoted_column = self.class.connection.quote_column_name(batch_column_name)
144
- batch_range = relation.pluck("MIN(#{quoted_column}), MAX(#{quoted_column})").first
145
- else
146
- min = relation.arel_table[batch_column_name].minimum
147
- max = relation.arel_table[batch_column_name].maximum
186
+ min = relation.arel_table[batch_column_name].minimum
187
+ max = relation.arel_table[batch_column_name].maximum
188
+ batch_range = relation.pick(min, max)
148
189
 
149
- batch_range = relation.pluck(min, max).first
150
- end
151
190
  break
152
191
  end
153
192
  # rubocop:enable Lint/UnreachableLoop
@@ -162,6 +201,10 @@ module OnlineMigrations
162
201
  [min_value, max_value]
163
202
  end
164
203
 
204
+ protected
205
+ attr_accessor :child
206
+ alias child? child
207
+
165
208
  private
166
209
  def validate_batch_column_values
167
210
  if max_value.to_i < min_value.to_i
@@ -176,7 +219,13 @@ module OnlineMigrations
176
219
  end
177
220
 
178
221
  def validate_jobs_status
179
- if succeeded? && migration_jobs.except_succeeded.exists?
222
+ if composite?
223
+ if succeeded? && children.except_succeeded.exists?
224
+ errors.add(:base, "all child migrations must be succeeded")
225
+ elsif failed? && !children.failed.exists?
226
+ errors.add(:base, "at least one child migration must be failed")
227
+ end
228
+ elsif succeeded? && migration_jobs.except_succeeded.exists?
180
229
  errors.add(:base, "all migration jobs must be succeeded")
181
230
  elsif failed? && !migration_jobs.failed.exists?
182
231
  errors.add(:base, "at least one migration job must be failed")
@@ -185,12 +234,30 @@ module OnlineMigrations
185
234
 
186
235
  def set_defaults
187
236
  if migration_relation.is_a?(ActiveRecord::Relation)
188
- self.batch_column_name ||= migration_relation.primary_key
189
- self.min_value ||= migration_relation.minimum(batch_column_name)
190
- self.max_value ||= migration_relation.maximum(batch_column_name)
191
-
192
- count = migration_object.count
193
- self.rows_count = count if count != :no_count
237
+ if !child?
238
+ shards = Utils.shard_names(migration_model)
239
+ self.composite = shards.size > 1
240
+ end
241
+
242
+ self.batch_column_name ||= migration_relation.primary_key
243
+
244
+ if composite?
245
+ self.min_value = self.max_value = self.rows_count = -1 # not relevant
246
+ else
247
+ on_shard do
248
+ self.min_value ||= migration_relation.minimum(batch_column_name)
249
+ self.max_value ||= migration_relation.maximum(batch_column_name)
250
+
251
+ # This can be the case when run in development on empty tables
252
+ if min_value.nil?
253
+ # integer IDs minimum value is 1
254
+ self.min_value = self.max_value = 1
255
+ end
256
+
257
+ count = migration_object.count
258
+ self.rows_count = count if count != :no_count
259
+ end
260
+ end
194
261
  end
195
262
 
196
263
  config = ::OnlineMigrations.config.background_migrations
@@ -199,12 +266,27 @@ module OnlineMigrations
199
266
  self.batch_pause ||= config.batch_pause
200
267
  self.sub_batch_pause_ms ||= config.sub_batch_pause_ms
201
268
  self.batch_max_attempts ||= config.batch_max_attempts
269
+ end
270
+
271
+ def create_child_migrations
272
+ shards = Utils.shard_names(migration_model)
202
273
 
203
- # This can be the case when run in development on empty tables
204
- if min_value.nil?
205
- # integer IDs minimum value is 1
206
- self.min_value = self.max_value = 1
274
+ children = shards.map do |shard|
275
+ child = Migration.new(migration_name: migration_name, arguments: arguments, shard: shard)
276
+ child.child = true
277
+ child
207
278
  end
279
+
280
+ self.children = children
281
+ end
282
+
283
+ def copy_attributes_to_children
284
+ attributes = [:batch_size, :sub_batch_size, :batch_pause, :sub_batch_pause_ms, :batch_max_attempts]
285
+ updates = {}
286
+ attributes.each do |attribute|
287
+ updates[attribute] = read_attribute(attribute) if attribute_changed?(attribute)
288
+ end
289
+ children.update_all(updates) if updates.any?
208
290
  end
209
291
 
210
292
  def next_min_value
@@ -214,6 +296,13 @@ module OnlineMigrations
214
296
  min_value
215
297
  end
216
298
  end
299
+
300
+ def find_abstract_class(model)
301
+ model.ancestors.find do |parent|
302
+ parent == ActiveRecord::Base ||
303
+ (parent.is_a?(Class) && parent.abstract_class?)
304
+ end
305
+ end
217
306
  end
218
307
  end
219
308
  end
@@ -223,10 +223,6 @@ module OnlineMigrations
223
223
  # For smaller tables it is probably better and easier to directly find and delete orpahed records.
224
224
  #
225
225
  def delete_orphaned_records_in_background(model_name, *associations, **options)
226
- if Utils.ar_version <= 4.2
227
- raise "#{__method__} does not support Active Record <= 4.2 yet"
228
- end
229
-
230
226
  model_name = model_name.name if model_name.is_a?(Class)
231
227
 
232
228
  enqueue_background_migration(
@@ -2,7 +2,7 @@
2
2
 
3
3
  module OnlineMigrations
4
4
  module BackgroundMigrations
5
- class MigrationJob < ActiveRecord::Base
5
+ class MigrationJob < ApplicationRecord
6
6
  STATUSES = [
7
7
  :enqueued,
8
8
  :running,
@@ -12,12 +12,11 @@ module OnlineMigrations
12
12
 
13
13
  self.table_name = :background_migration_jobs
14
14
 
15
- # For Active Record <= 4.2 needs to fully specify enum values
16
- scope :active, -> { where(status: [statuses[:enqueued], statuses[:running]]) }
17
- scope :completed, -> { where(status: [statuses[:failed], statuses[:succeeded]]) }
15
+ scope :active, -> { where(status: [:enqueued, :running]) }
16
+ scope :completed, -> { where(status: [:failed, :succeeded]) }
18
17
  scope :stuck, -> do
19
- timeout = ::OnlineMigrations.config.background_migrations.stuck_jobs_timeout
20
- active.where("updated_at <= ?", timeout.ago)
18
+ timeout = OnlineMigrations.config.background_migrations.stuck_jobs_timeout
19
+ active.where("updated_at <= ?", timeout.seconds.ago)
21
20
  end
22
21
 
23
22
  scope :retriable, -> do
@@ -26,7 +25,7 @@ module OnlineMigrations
26
25
  stuck_sql = connection.unprepared_statement { stuck.to_sql }
27
26
  failed_retriable_sql = connection.unprepared_statement { failed_retriable.to_sql }
28
27
 
29
- from(Arel.sql(<<-SQL.strip_heredoc))
28
+ from(Arel.sql(<<~SQL))
30
29
  (
31
30
  (#{failed_retriable_sql})
32
31
  UNION
@@ -35,19 +34,16 @@ module OnlineMigrations
35
34
  SQL
36
35
  end
37
36
 
38
- scope :except_succeeded, -> { where("status != ?", statuses[:succeeded]) }
37
+ scope :except_succeeded, -> { where.not(status: :succeeded) }
39
38
  scope :attempts_exceeded, -> { where("attempts >= max_attempts") }
40
39
 
41
- enum status: STATUSES.map { |status| [status, status.to_s] }.to_h
40
+ enum status: STATUSES.index_with(&:to_s)
42
41
 
43
42
  delegate :migration_class, :migration_object, :migration_relation, :batch_column_name,
44
43
  :arguments, :batch_pause, to: :migration
45
44
 
46
45
  belongs_to :migration
47
46
 
48
- # For Active Record 5.0+ this is validated by default from belongs_to
49
- validates :migration, presence: true
50
-
51
47
  validates :min_value, :max_value, presence: true, numericality: { greater_than: 0 }
52
48
  validate :values_in_migration_range, if: :min_value?
53
49
  validate :validate_values_order, if: :min_value?
@@ -56,6 +52,13 @@ module OnlineMigrations
56
52
 
57
53
  before_create :copy_settings_from_migration
58
54
 
55
+ # Whether the job is considered stuck (is running for some configured time).
56
+ #
57
+ def stuck?
58
+ timeout = OnlineMigrations.config.background_migrations.stuck_jobs_timeout
59
+ running? && updated_at <= timeout.seconds.ago
60
+ end
61
+
59
62
  # Mark this job as ready to be processed again.
60
63
  #
61
64
  # This is used when retrying failed jobs.
@@ -6,7 +6,7 @@ module OnlineMigrations
6
6
  class MigrationJobRunner
7
7
  attr_reader :migration_job
8
8
 
9
- delegate :attempts, :migration_relation, :migration_object, :sub_batch_size,
9
+ delegate :migration, :attempts, :migration_relation, :migration_object, :sub_batch_size,
10
10
  :batch_column_name, :min_value, :max_value, :pause_ms, to: :migration_job
11
11
 
12
12
  def initialize(migration_job)
@@ -30,7 +30,7 @@ module OnlineMigrations
30
30
  )
31
31
 
32
32
  ActiveSupport::Notifications.instrument("process_batch.background_migrations", job_payload) do
33
- run_batch
33
+ migration.on_shard { run_batch }
34
34
  end
35
35
 
36
36
  migration_job.update!(status: :succeeded, finished_at: Time.current)
@@ -12,8 +12,13 @@ module OnlineMigrations
12
12
 
13
13
  # Runs one background migration job.
14
14
  def run_migration_job
15
- migration.running! if migration.enqueued?
16
- migration_payload = { background_migration: migration }
15
+ raise "Should not be called on a composite (with sharding) migration" if migration.composite?
16
+
17
+ if migration.enqueued?
18
+ migration.running!
19
+ migration.parent.running! if migration.parent && migration.parent.enqueued?
20
+ end
21
+ migration_payload = notifications_payload(migration)
17
22
 
18
23
  if !migration.migration_jobs.exists?
19
24
  ActiveSupport::Notifications.instrument("started.background_migrations", migration_payload)
@@ -37,6 +42,8 @@ module OnlineMigrations
37
42
  end
38
43
 
39
44
  ActiveSupport::Notifications.instrument("completed.background_migrations", migration_payload)
45
+
46
+ complete_parent_if_needed(migration) if migration.parent.present?
40
47
  end
41
48
 
42
49
  next_migration_job
@@ -52,8 +59,15 @@ module OnlineMigrations
52
59
 
53
60
  migration.running!
54
61
 
55
- while migration.running?
56
- run_migration_job
62
+ if migration.composite?
63
+ migration.children.each do |child_migration|
64
+ runner = self.class.new(child_migration)
65
+ runner.run_all_migration_jobs
66
+ end
67
+ else
68
+ while migration.running?
69
+ run_migration_job
70
+ end
57
71
  end
58
72
  end
59
73
 
@@ -64,13 +78,20 @@ module OnlineMigrations
64
78
  def finish
65
79
  return if migration.completed?
66
80
 
67
- # Mark is as finishing to avoid being picked up
68
- # by the background migrations scheduler.
69
- migration.finishing!
70
- migration.reset_failed_jobs_attempts
71
-
72
- while migration.finishing?
73
- run_migration_job
81
+ if migration.composite?
82
+ migration.children.each do |child_migration|
83
+ runner = self.class.new(child_migration)
84
+ runner.finish
85
+ end
86
+ else
87
+ # Mark is as finishing to avoid being picked up
88
+ # by the background migrations scheduler.
89
+ migration.finishing!
90
+ migration.reset_failed_jobs_attempts
91
+
92
+ while migration.finishing?
93
+ run_migration_job
94
+ end
74
95
  end
75
96
  end
76
97
 
@@ -95,6 +116,30 @@ module OnlineMigrations
95
116
  max_value: max_value
96
117
  )
97
118
  end
119
+
120
+ def complete_parent_if_needed(migration)
121
+ parent = migration.parent
122
+ completed = false
123
+
124
+ parent.with_lock do
125
+ children = parent.children.select(:status)
126
+ if children.all?(&:succeeded?)
127
+ parent.succeeded!
128
+ completed = true
129
+ elsif children.any?(&:failed?)
130
+ parent.failed!
131
+ completed = true
132
+ end
133
+ end
134
+
135
+ if completed
136
+ ActiveSupport::Notifications.instrument("completed.background_migrations", notifications_payload(migration))
137
+ end
138
+ end
139
+
140
+ def notifications_payload(migration)
141
+ { background_migration: migration }
142
+ end
98
143
  end
99
144
  end
100
145
  end
@@ -26,7 +26,7 @@ module OnlineMigrations
26
26
  counter_name = reflection.counter_cache_column
27
27
 
28
28
  quoted_association_table = connection.quote_table_name(has_many_association.table_name)
29
- count_subquery = <<-SQL.strip_heredoc
29
+ count_subquery = <<~SQL
30
30
  SELECT COUNT(*)
31
31
  FROM #{quoted_association_table}
32
32
  WHERE #{quoted_association_table}.#{connection.quote_column_name(foreign_key)} =
@@ -41,8 +41,7 @@ module OnlineMigrations
41
41
  names = Array.wrap(names)
42
42
  options = names.extract_options!
43
43
  touch_updates = touch_attributes_with_time(*names, **options)
44
- # In Active Record 4.2 sanitize_sql_for_assignment is protected
45
- updates << model.send(:sanitize_sql_for_assignment, touch_updates)
44
+ updates << model.sanitize_sql_for_assignment(touch_updates)
46
45
  end
47
46
 
48
47
  relation.update_all(updates.join(", "))
@@ -64,11 +63,6 @@ module OnlineMigrations
64
63
 
65
64
  has_many_association = has_many.find do |association|
66
65
  counter_cache_column = association.counter_cache_column
67
-
68
- # Active Record <= 4.2 is able to return only explicitly provided `counter_cache` column.
69
- if !counter_cache_column && Utils.ar_version <= 4.2
70
- counter_cache_column = "#{association.name}_count"
71
- end
72
66
  counter_cache_column && counter_cache_column.to_sym == counter_association.to_sym
73
67
  end
74
68
 
@@ -86,7 +80,7 @@ module OnlineMigrations
86
80
  def touch_attributes_with_time(*names, time: nil)
87
81
  attribute_names = timestamp_attributes_for_update & model.column_names
88
82
  attribute_names |= names.map(&:to_s)
89
- attribute_names.map { |attribute_name| [attribute_name, time || Time.current] }.to_h
83
+ attribute_names.index_with(time || Time.current)
90
84
  end
91
85
 
92
86
  def timestamp_attributes_for_update
@@ -4,15 +4,14 @@ module OnlineMigrations
4
4
  module BackgroundMigrations
5
5
  # Class responsible for scheduling background migrations.
6
6
  # It selects runnable background migrations and runs them one step (one batch) at a time.
7
- # A migration is considered runnable if it is not completed and time the interval between
7
+ # A migration is considered runnable if it is not completed and the time interval between
8
8
  # successive runs has passed.
9
- # Scheduler ensures (via advisory locks) that at most one background migration at a time is running per database.
10
9
  #
11
- # Scheduler should be run via some kind of periodical means, for example, cron.
10
+ # Scheduler should be configured to run periodically, for example, via cron.
12
11
  # @example Run via whenever
13
12
  # # add this to schedule.rb
14
13
  # every 1.minute do
15
- # runner "OnlineMigrations::BackgroundMigrations::Scheduler.run"
14
+ # runner "OnlineMigrations.run_background_migrations"
16
15
  # end
17
16
  #
18
17
  class Scheduler
@@ -22,24 +21,15 @@ module OnlineMigrations
22
21
 
23
22
  # Runs Scheduler
24
23
  def run
25
- active_migrations = Migration.active.queue_order
24
+ active_migrations = Migration.runnable.active.queue_order
26
25
  runnable_migrations = active_migrations.select(&:interval_elapsed?)
27
26
 
28
27
  runnable_migrations.each do |migration|
29
- connection = migration.migration_relation.connection
30
-
31
- with_exclusive_lock(connection) do
32
- run_migration_job(migration)
33
- end
28
+ run_migration_job(migration)
34
29
  end
35
30
  end
36
31
 
37
32
  private
38
- def with_exclusive_lock(connection, &block)
39
- lock = AdvisoryLock.new(name: "online_migrations_scheduler", connection: connection)
40
- lock.with_lock(&block)
41
- end
42
-
43
33
  def run_migration_job(migration)
44
34
  runner = MigrationRunner.new(migration)
45
35
  runner.run_migration_job