online_migrations 0.25.0 → 0.27.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README.md +18 -73
  4. data/docs/0.27-upgrade.md +24 -0
  5. data/docs/background_data_migrations.md +200 -101
  6. data/docs/background_schema_migrations.md +2 -2
  7. data/docs/configuring.md +8 -0
  8. data/lib/generators/online_migrations/{background_migration_generator.rb → data_migration_generator.rb} +4 -4
  9. data/lib/generators/online_migrations/templates/add_sharding_to_online_migrations.rb.tt +1 -1
  10. data/lib/generators/online_migrations/templates/add_timestamps_to_background_migrations.rb.tt +1 -1
  11. data/lib/generators/online_migrations/templates/background_schema_migrations_change_unique_index.rb.tt +1 -1
  12. data/lib/generators/online_migrations/templates/change_background_data_migrations.rb.tt +34 -0
  13. data/lib/generators/online_migrations/templates/{background_data_migration.rb.tt → data_migration.rb.tt} +8 -9
  14. data/lib/generators/online_migrations/templates/initializer.rb.tt +22 -25
  15. data/lib/generators/online_migrations/templates/install_migration.rb.tt +9 -40
  16. data/lib/generators/online_migrations/upgrade_generator.rb +16 -8
  17. data/lib/online_migrations/active_record_batch_enumerator.rb +8 -0
  18. data/lib/online_migrations/background_data_migrations/backfill_column.rb +50 -0
  19. data/lib/online_migrations/background_data_migrations/config.rb +62 -0
  20. data/lib/online_migrations/{background_migrations → background_data_migrations}/copy_column.rb +15 -28
  21. data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_associated_records.rb +9 -5
  22. data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_orphaned_records.rb +5 -9
  23. data/lib/online_migrations/background_data_migrations/migration.rb +312 -0
  24. data/lib/online_migrations/{background_migrations → background_data_migrations}/migration_helpers.rb +72 -61
  25. data/lib/online_migrations/background_data_migrations/migration_job.rb +158 -0
  26. data/lib/online_migrations/background_data_migrations/migration_status_validator.rb +65 -0
  27. data/lib/online_migrations/{background_migrations → background_data_migrations}/perform_action_on_relation.rb +5 -5
  28. data/lib/online_migrations/{background_migrations → background_data_migrations}/reset_counters.rb +5 -5
  29. data/lib/online_migrations/background_data_migrations/scheduler.rb +78 -0
  30. data/lib/online_migrations/background_data_migrations/ticker.rb +62 -0
  31. data/lib/online_migrations/background_schema_migrations/config.rb +2 -2
  32. data/lib/online_migrations/background_schema_migrations/migration.rb +57 -127
  33. data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +26 -47
  34. data/lib/online_migrations/background_schema_migrations/migration_runner.rb +43 -97
  35. data/lib/online_migrations/background_schema_migrations/scheduler.rb +2 -2
  36. data/lib/online_migrations/batch_iterator.rb +7 -4
  37. data/lib/online_migrations/change_column_type_helpers.rb +75 -14
  38. data/lib/online_migrations/command_checker.rb +32 -20
  39. data/lib/online_migrations/config.rb +12 -4
  40. data/lib/online_migrations/data_migration.rb +127 -0
  41. data/lib/online_migrations/error_messages.rb +16 -0
  42. data/lib/online_migrations/index_definition.rb +1 -1
  43. data/lib/online_migrations/lock_retrier.rb +5 -2
  44. data/lib/online_migrations/migration.rb +8 -1
  45. data/lib/online_migrations/schema_cache.rb +0 -78
  46. data/lib/online_migrations/schema_statements.rb +18 -74
  47. data/lib/online_migrations/shard_aware.rb +44 -0
  48. data/lib/online_migrations/utils.rb +1 -20
  49. data/lib/online_migrations/verbose_sql_logs.rb +1 -7
  50. data/lib/online_migrations/version.rb +1 -1
  51. data/lib/online_migrations.rb +19 -19
  52. metadata +25 -24
  53. data/lib/online_migrations/background_migration.rb +0 -64
  54. data/lib/online_migrations/background_migrations/backfill_column.rb +0 -54
  55. data/lib/online_migrations/background_migrations/background_migration_class_validator.rb +0 -29
  56. data/lib/online_migrations/background_migrations/config.rb +0 -74
  57. data/lib/online_migrations/background_migrations/migration.rb +0 -329
  58. data/lib/online_migrations/background_migrations/migration_job.rb +0 -109
  59. data/lib/online_migrations/background_migrations/migration_job_runner.rb +0 -66
  60. data/lib/online_migrations/background_migrations/migration_job_status_validator.rb +0 -29
  61. data/lib/online_migrations/background_migrations/migration_runner.rb +0 -161
  62. data/lib/online_migrations/background_migrations/migration_status_validator.rb +0 -48
  63. data/lib/online_migrations/background_migrations/scheduler.rb +0 -42
@@ -8,13 +8,15 @@ module OnlineMigrations
8
8
  # `enqueue_background_schema_migration` helper inside migrations.
9
9
  #
10
10
  class Migration < ApplicationRecord
11
+ include ShardAware
12
+
11
13
  STATUSES = [
12
- :enqueued, # The migration has been enqueued by the user.
13
- :running, # The migration is being performed by a migration executor.
14
- :errored, # The migration raised an error during last run.
15
- :failed, # The migration raises an error when running and retry attempts exceeded.
16
- :succeeded, # The migration finished without error.
17
- :cancelled, # The migration was cancelled by the user.
14
+ "enqueued", # The migration has been enqueued by the user.
15
+ "running", # The migration is being performed by a migration executor.
16
+ "errored", # The migration raised an error during last run.
17
+ "failed", # The migration raises an error when running and retry attempts exceeded.
18
+ "succeeded", # The migration finished without error.
19
+ "cancelled", # The migration was cancelled by the user.
18
20
  ]
19
21
 
20
22
  MAX_IDENTIFIER_LENGTH = 63
@@ -22,36 +24,12 @@ module OnlineMigrations
22
24
  self.table_name = :background_schema_migrations
23
25
 
24
26
  scope :queue_order, -> { order(created_at: :asc) }
25
- scope :parents, -> { where(parent_id: nil) }
26
- scope :runnable, -> { where(composite: false) }
27
27
  scope :active, -> { where(status: [:enqueued, :running, :errored]) }
28
- scope :except_succeeded, -> { where.not(status: :succeeded) }
29
-
30
- scope :stuck, -> do
31
- runnable.active.where(<<~SQL)
32
- updated_at <= NOW() - interval '1 second' * (COALESCE(statement_timeout, 60*60*24) + 60*10)
33
- SQL
34
- end
35
-
36
- scope :retriable, -> do
37
- stuck_sql = connection.unprepared_statement { stuck.to_sql }
38
-
39
- from(Arel.sql(<<~SQL))
40
- (
41
- (SELECT * FROM background_schema_migrations WHERE NOT composite AND status = 'errored')
42
- UNION
43
- (#{stuck_sql})
44
- ) AS #{table_name}
45
- SQL
46
- end
47
28
 
48
29
  alias_attribute :name, :migration_name
49
30
 
50
31
  enum :status, STATUSES.index_with(&:to_s)
51
32
 
52
- belongs_to :parent, class_name: name, optional: true, inverse_of: :children
53
- has_many :children, class_name: name, foreign_key: :parent_id, inverse_of: :parent
54
-
55
33
  validates :table_name, presence: true, length: { maximum: MAX_IDENTIFIER_LENGTH }
56
34
  validates :definition, presence: true
57
35
  validates :migration_name, presence: true, uniqueness: {
@@ -65,70 +43,53 @@ module OnlineMigrations
65
43
  end,
66
44
  }
67
45
 
68
- validate :validate_children_statuses, if: -> { composite? && status_changed? }
69
- validate :validate_connection_class, if: :connection_class_name?
70
46
  validate :validate_table_exists
71
47
  validates_with MigrationStatusValidator, on: :update
72
48
 
73
49
  before_validation :set_defaults
74
50
 
51
+ # Returns whether the migration is completed, which is defined as
52
+ # having a status of succeeded, failed, or cancelled.
53
+ #
54
+ # @return [Boolean] whether the migration is completed.
55
+ #
75
56
  def completed?
76
- succeeded? || failed?
57
+ succeeded? || failed? || cancelled?
77
58
  end
78
59
 
79
- # Overwrite enum's generated method to correctly work for composite migrations.
80
- def cancelled!
81
- return super if !composite?
82
-
83
- transaction do
84
- super
85
- children.each { |child| child.cancelled! if !child.succeeded? }
86
- end
60
+ # Returns whether the migration is active, which is defined as
61
+ # having a status of enqueued, or running.
62
+ #
63
+ # @return [Boolean] whether the migration is active.
64
+ #
65
+ def active?
66
+ enqueued? || running?
87
67
  end
68
+
88
69
  alias cancel cancelled!
89
70
 
71
+ # Returns whether this migration is pausable.
72
+ #
90
73
  def pausable?
91
74
  false
92
75
  end
93
76
 
94
- def can_be_paused?
95
- false
96
- end
97
-
98
- def can_be_cancelled?
99
- !succeeded? && !cancelled?
100
- end
101
-
102
- # Returns the progress of the background schema migration.
77
+ # Dummy method to support the same interface as background data migrations.
103
78
  #
104
- # @return [Float] value in range from 0.0 to 100.0
79
+ # @return [nil]
105
80
  #
106
81
  def progress
107
- if succeeded?
108
- 100.0
109
- elsif composite?
110
- progresses = children.map(&:progress)
111
- # There should not be composite migrations without children,
112
- # but children may be deleted for some reason, so we need to
113
- # make a check to avoid 0 division error.
114
- if progresses.any?
115
- (progresses.sum.to_f / progresses.size).round(2)
116
- else
117
- 0.0
118
- end
119
- else
120
- 0.0
121
- end
122
82
  end
123
83
 
124
84
  # Whether the migration is considered stuck (is running for some configured time).
125
85
  #
126
86
  def stuck?
127
- # Composite migrations are not considered stuck.
128
- return false if composite?
129
-
130
- stuck_timeout = (statement_timeout || 1.day) + 10.minutes
131
- running? && updated_at <= stuck_timeout.seconds.ago
87
+ if index_addition?
88
+ running? && !index_build_in_progress?
89
+ else
90
+ stuck_timeout = (statement_timeout || 1.day) + 10.minutes
91
+ running? && updated_at <= stuck_timeout.seconds.ago
92
+ end
132
93
  end
133
94
 
134
95
  # Mark this migration as ready to be processed again.
@@ -136,27 +97,16 @@ module OnlineMigrations
136
97
  # This is used to manually retrying failed migrations.
137
98
  #
138
99
  def retry
139
- if composite? && failed?
140
- transaction do
141
- update!(status: :enqueued, finished_at: nil)
142
- children.failed.each(&:retry)
143
- end
144
-
145
- true
146
- elsif failed?
147
- transaction do
148
- parent.update!(status: :enqueued, finished_at: nil) if parent
149
-
150
- update!(
151
- status: :enqueued,
152
- attempts: 0,
153
- started_at: nil,
154
- finished_at: nil,
155
- error_class: nil,
156
- error_message: nil,
157
- backtrace: nil
158
- )
159
- end
100
+ if failed?
101
+ update!(
102
+ status: :enqueued,
103
+ attempts: 0,
104
+ started_at: nil,
105
+ finished_at: nil,
106
+ error_class: nil,
107
+ error_message: nil,
108
+ backtrace: nil
109
+ )
160
110
 
161
111
  true
162
112
  else
@@ -168,15 +118,6 @@ module OnlineMigrations
168
118
  definition.match?(/create (unique )?index/i)
169
119
  end
170
120
 
171
- # @private
172
- def connection_class
173
- if connection_class_name && (klass = connection_class_name.safe_constantize)
174
- Utils.find_connection_class(klass)
175
- else
176
- ActiveRecord::Base
177
- end
178
- end
179
-
180
121
  # @private
181
122
  def attempts_exceeded?
182
123
  attempts >= max_attempts
@@ -184,7 +125,7 @@ module OnlineMigrations
184
125
 
185
126
  # @private
186
127
  def run
187
- on_shard do
128
+ on_shard_if_present do
188
129
  connection = connection_class.connection
189
130
 
190
131
  connection.with_lock_retries do
@@ -194,10 +135,7 @@ module OnlineMigrations
194
135
  if index_addition?
195
136
  index = connection.indexes(table_name).find { |i| i.name == name }
196
137
  if index
197
- # Use index validity from https://github.com/rails/rails/pull/45160
198
- # when switching to ActiveRecord >= 7.1.
199
- schema = connection.send(:__schema_for_table, table_name)
200
- if connection.send(:__index_valid?, name, schema: schema)
138
+ if index.valid?
201
139
  return
202
140
  else
203
141
  connection.remove_index(table_name, name: name, algorithm: :concurrently)
@@ -206,34 +144,22 @@ module OnlineMigrations
206
144
  end
207
145
 
208
146
  connection.execute(definition)
147
+
148
+ # Outdated statistics + a new index can hurt performance of existing queries.
149
+ if OnlineMigrations.config.auto_analyze
150
+ connection.execute("ANALYZE #{table_name}")
151
+ end
209
152
  end
210
153
  end
211
154
  end
212
155
  end
213
156
 
214
157
  private
215
- def validate_children_statuses
216
- if composite?
217
- if succeeded? && children.except_succeeded.exists?
218
- errors.add(:base, "all child migrations must be succeeded")
219
- elsif failed? && !children.failed.exists?
220
- errors.add(:base, "at least one child migration must be failed")
221
- end
222
- end
223
- end
224
-
225
- def validate_connection_class
226
- klass = connection_class_name.safe_constantize
227
- if !(klass <= ActiveRecord::Base)
228
- errors.add(:connection_class_name, "is not an ActiveRecord::Base child class")
229
- end
230
- end
231
-
232
158
  def validate_table_exists
233
159
  # Skip this validation if we have invalid connection class name.
234
160
  return if errors.include?(:connection_class_name)
235
161
 
236
- on_shard do
162
+ on_shard_if_present do
237
163
  if !connection_class.connection.table_exists?(table_name)
238
164
  errors.add(:table_name, "'#{table_name}' does not exist")
239
165
  end
@@ -246,9 +172,13 @@ module OnlineMigrations
246
172
  self.statement_timeout ||= config.statement_timeout
247
173
  end
248
174
 
249
- def on_shard(&block)
250
- shard = (self.shard || connection_class.default_shard).to_sym
251
- connection_class.connected_to(shard: shard, role: :writing, &block)
175
+ def index_build_in_progress?
176
+ indexes_in_progress = connection_class.connection.select_values(<<~SQL)
177
+ SELECT index_relid::regclass::text
178
+ FROM pg_stat_progress_create_index
179
+ SQL
180
+
181
+ indexes_in_progress.include?(name)
252
182
  end
253
183
 
254
184
  def with_statement_timeout(connection, timeout)
@@ -19,7 +19,7 @@ module OnlineMigrations
19
19
  return
20
20
  end
21
21
 
22
- if index_exists?(table_name, column_name, **options)
22
+ if index_exists?(table_name, column_name, name: index.name, **options)
23
23
  Utils.raise_or_say("Index creation was not enqueued because the index already exists.")
24
24
  return
25
25
  end
@@ -78,67 +78,46 @@ module OnlineMigrations
78
78
  # ensure_background_schema_migration_succeeded("index_users_on_email")
79
79
  #
80
80
  def ensure_background_schema_migration_succeeded(migration_name)
81
- migration = Migration.parents.find_by(migration_name: migration_name)
81
+ migrations = Migration.where(migration_name: migration_name).to_a
82
82
 
83
- if migration.nil?
84
- Utils.raise_in_prod_or_say_in_dev("Could not find background schema migration: '#{migration_name}'")
85
- elsif !migration.succeeded?
86
- raise "Expected background schema migration '#{migration_name}' to be marked as 'succeeded', " \
87
- "but it is '#{migration.status}'."
83
+ if migrations.empty?
84
+ Utils.raise_in_prod_or_say_in_dev("Could not find background schema migration(s): '#{migration_name}'.")
85
+ elsif !migrations.all?(&:succeeded?)
86
+ raise "Expected background schema migration(s) '#{migration_name}' to be marked as 'succeeded'."
88
87
  end
89
88
  end
90
89
 
91
- def enqueue_background_schema_migration(name, table_name, **options)
92
- if options[:connection_class_name].nil? && Utils.multiple_databases?
93
- raise ArgumentError, "You must pass a :connection_class_name when using multiple databases."
94
- end
95
-
96
- migration = create_background_schema_migration(name, table_name, **options)
90
+ def enqueue_background_schema_migration(migration_name, table_name, connection_class_name: nil, **options)
91
+ options.assert_valid_keys(:definition, :max_attempts, :statement_timeout)
97
92
 
98
- run_inline = OnlineMigrations.config.run_background_migrations_inline
99
- if run_inline && run_inline.call
100
- runner = MigrationRunner.new(migration)
101
- runner.run
93
+ if Utils.multiple_databases? && !connection_class_name
94
+ raise ArgumentError, "You must pass a :connection_class_name when using multiple databases."
102
95
  end
103
96
 
104
- migration
105
- end
106
-
107
- # @private
108
- def create_background_schema_migration(migration_name, table_name, connection_class_name: nil, **options)
109
- options.assert_valid_keys(:definition, :max_attempts, :statement_timeout)
110
-
111
97
  if connection_class_name
112
- connection_class_name = __normalize_connection_class_name(connection_class_name)
98
+ klass = connection_class_name.constantize
99
+ connection_class = Utils.find_connection_class(klass)
100
+ # Normalize to the real connection class name.
101
+ connection_class_name = connection_class.name
102
+ else
103
+ connection_class = ActiveRecord::Base
113
104
  end
114
105
 
115
- Migration.find_or_create_by!(migration_name: migration_name, shard: nil,
116
- connection_class_name: connection_class_name) do |migration|
117
- migration.assign_attributes(**options, table_name: table_name)
106
+ shards = Utils.shard_names(connection_class)
107
+ shards = [nil] if shards.size == 1
118
108
 
119
- shards = Utils.shard_names(migration.connection_class)
120
- if shards.size > 1
121
- migration.children = shards.map do |shard|
122
- child = migration.dup
123
- child.shard = shard
124
- child
125
- end
109
+ shards.each do |shard|
110
+ migration = Migration.create_with(**options, table_name: table_name)
111
+ .find_or_create_by!(migration_name: migration_name, shard: shard, connection_class_name: connection_class_name)
126
112
 
127
- migration.composite = true
113
+ if Utils.run_background_migrations_inline?
114
+ runner = MigrationRunner.new(migration)
115
+ runner.run
128
116
  end
129
117
  end
130
- end
131
118
 
132
- private
133
- def __normalize_connection_class_name(connection_class_name)
134
- if connection_class_name
135
- klass = connection_class_name.safe_constantize
136
- if klass
137
- connection_class = Utils.find_connection_class(klass)
138
- connection_class.name if connection_class
139
- end
140
- end
141
- end
119
+ true
120
+ end
142
121
  end
143
122
  end
144
123
  end
@@ -13,119 +13,65 @@ module OnlineMigrations
13
13
  def run
14
14
  return if migration.cancelled? || migration.succeeded?
15
15
 
16
- mark_as_running if migration.enqueued? || migration.errored?
16
+ migration.running! if migration.enqueued? || migration.errored?
17
+ migration_payload = { migration: migration }
17
18
 
18
- if migration.composite?
19
- migration.children.each do |child_migration|
20
- runner = self.class.new(child_migration)
21
- runner.run
22
- end
19
+ if migration.attempts == 0
20
+ ActiveSupport::Notifications.instrument("started.background_schema_migrations", migration_payload)
23
21
  else
24
- do_run
22
+ ActiveSupport::Notifications.instrument("retried.background_schema_migrations", migration_payload)
25
23
  end
26
- end
27
-
28
- private
29
- def mark_as_running
30
- Migration.transaction do
31
- migration.running!
32
24
 
33
- if (parent = migration.parent)
34
- if parent.started_at
35
- parent.update!(status: :running, finished_at: nil)
36
- else
37
- parent.update!(status: :running, started_at: Time.current, finished_at: nil)
38
- end
39
- end
40
- end
25
+ if should_throttle?
26
+ ActiveSupport::Notifications.instrument("throttled.background_schema_migrations", migration_payload)
27
+ return
41
28
  end
42
29
 
43
- def do_run
44
- migration_payload = notifications_payload(migration)
45
-
46
- if migration.attempts == 0
47
- ActiveSupport::Notifications.instrument("started.background_schema_migrations", migration_payload)
48
- else
49
- ActiveSupport::Notifications.instrument("retried.background_schema_migrations", migration_payload)
50
- end
51
-
52
- if should_throttle?
53
- ActiveSupport::Notifications.instrument("throttled.background_schema_migrations", migration_payload)
54
- return
55
- end
56
-
57
- migration.update!(
58
- attempts: migration.attempts + 1,
59
- status: :running,
60
- started_at: Time.current,
61
- finished_at: nil,
62
- error_class: nil,
63
- error_message: nil,
64
- backtrace: nil
65
- )
66
-
67
- ActiveSupport::Notifications.instrument("run.background_schema_migrations", migration_payload) do
68
- migration.run
69
- end
70
-
71
- # Background schema migrations could take a while to run. It is possible, that the process
72
- # never reaches this (or the rescue below) line of code. E.g., when it is force quitted
73
- # (SIGKILL etc.) and so the migration will end up in the "running" state and the query is
74
- # still executing (or already finished) in the database. This migration can either be safely
75
- # manually retried or will be picked up in the future by scheduler when it decides that
76
- # this migration is stuck.
77
-
78
- migration.update!(status: :succeeded, finished_at: Time.current)
30
+ migration.update!(
31
+ attempts: migration.attempts + 1,
32
+ status: :running,
33
+ started_at: Time.current,
34
+ finished_at: nil,
35
+ error_class: nil,
36
+ error_message: nil,
37
+ backtrace: nil
38
+ )
39
+
40
+ ActiveSupport::Notifications.instrument("run.background_schema_migrations", migration_payload) do
41
+ migration.run
42
+ end
79
43
 
80
- ActiveSupport::Notifications.instrument("completed.background_schema_migrations", migration_payload)
44
+ # Background schema migrations could take a while to run. It is possible, that the process
45
+ # never reaches this (or the rescue below) line of code. E.g., when it is force quitted
46
+ # (SIGKILL etc.) and so the migration will end up in the "running" state and the query is
47
+ # still executing (or already finished) in the database. This migration can either be safely
48
+ # manually retried or will be picked up in the future by scheduler when it decides that
49
+ # this migration is stuck.
81
50
 
82
- complete_parent_if_needed(migration) if migration.parent.present?
83
- rescue Exception => e # rubocop:disable Lint/RescueException
84
- backtrace_cleaner = ::OnlineMigrations.config.backtrace_cleaner
51
+ migration.update!(status: :succeeded, finished_at: Time.current)
85
52
 
86
- status = migration.attempts_exceeded? ? :failed : :errored
53
+ ActiveSupport::Notifications.instrument("completed.background_schema_migrations", migration_payload)
54
+ rescue Exception => e # rubocop:disable Lint/RescueException
55
+ backtrace_cleaner = ::OnlineMigrations.config.backtrace_cleaner
87
56
 
88
- migration.update!(
89
- status: status,
90
- finished_at: Time.current,
91
- error_class: e.class.name,
92
- error_message: e.message,
93
- backtrace: backtrace_cleaner ? backtrace_cleaner.clean(e.backtrace) : e.backtrace
94
- )
57
+ status = migration.attempts_exceeded? ? :failed : :errored
95
58
 
96
- complete_parent_if_needed(migration) if migration.parent.present?
59
+ migration.update!(
60
+ status: status,
61
+ finished_at: Time.current,
62
+ error_class: e.class.name,
63
+ error_message: e.message,
64
+ backtrace: backtrace_cleaner ? backtrace_cleaner.clean(e.backtrace) : e.backtrace
65
+ )
97
66
 
98
- ::OnlineMigrations.config.background_schema_migrations.error_handler.call(e, migration)
99
- raise if Utils.run_background_migrations_inline?
100
- end
67
+ ::OnlineMigrations.config.background_schema_migrations.error_handler.call(e, migration)
68
+ raise if Utils.run_background_migrations_inline?
69
+ end
101
70
 
71
+ private
102
72
  def should_throttle?
103
73
  ::OnlineMigrations.config.throttler.call
104
74
  end
105
-
106
- def complete_parent_if_needed(migration)
107
- parent = migration.parent
108
- completed = false
109
-
110
- parent.with_lock do
111
- children = parent.children.select(:status)
112
- if children.all?(&:succeeded?)
113
- parent.update!(status: :succeeded, finished_at: Time.current)
114
- completed = true
115
- elsif children.any?(&:failed?)
116
- parent.update!(status: :failed, finished_at: Time.current)
117
- completed = true
118
- end
119
- end
120
-
121
- if completed
122
- ActiveSupport::Notifications.instrument("completed.background_migrations", notifications_payload(migration))
123
- end
124
- end
125
-
126
- def notifications_payload(migration)
127
- { background_schema_migration: migration }
128
- end
129
75
  end
130
76
  end
131
77
  end
@@ -35,8 +35,8 @@ module OnlineMigrations
35
35
 
36
36
  private
37
37
  def find_migration(**options)
38
- active_migrations = Migration.running.reject(&:stuck?)
39
- runnable_migrations = Migration.runnable.enqueued.queue_order.to_a + Migration.retriable.queue_order.to_a
38
+ stuck_migrations, active_migrations = Migration.running.partition(&:stuck?)
39
+ runnable_migrations = (Migration.enqueued + Migration.errored + stuck_migrations).sort_by(&:created_at)
40
40
 
41
41
  if options.key?(:shard)
42
42
  runnable_migrations = runnable_migrations.select { |migration| migration.shard.to_s == options[:shard].to_s }
@@ -19,8 +19,7 @@ module OnlineMigrations
19
19
  end
20
20
 
21
21
  relation = apply_limits(self.relation, column, start, finish, order)
22
- unscopes = Utils.ar_version < 7.1 ? [:includes] : [:includes, :preload, :eager_load]
23
- base_relation = relation.unscope(*unscopes).reselect(column).reorder(column => order)
22
+ base_relation = relation.unscope(:includes, :preload, :eager_load).reselect(column).reorder(column => order)
24
23
 
25
24
  start_id = start || begin
26
25
  start_row = base_relation.uncached { base_relation.first }
@@ -84,12 +83,16 @@ module OnlineMigrations
84
83
 
85
84
  private
86
85
  def apply_limits(relation, column, start, finish, order)
86
+ arel_column = relation.arel_table[column]
87
+
87
88
  if start
88
- relation = relation.where(relation.arel_table[column].public_send((order == :asc ? :gteq : :lteq), start))
89
+ predicate = order == :asc ? :gteq : :lteq
90
+ relation = relation.where(arel_column.public_send(predicate, start))
89
91
  end
90
92
 
91
93
  if finish
92
- relation = relation.where(relation.arel_table[column].public_send((order == :asc ? :lteq : :gteq), finish))
94
+ predicate = order == :asc ? :lteq : :gteq
95
+ relation = relation.where(arel_column.public_send(predicate, finish))
93
96
  end
94
97
 
95
98
  relation