online_migrations 0.26.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/docs/0.27-upgrade.md +24 -0
- data/docs/background_data_migrations.md +200 -101
- data/docs/background_schema_migrations.md +2 -2
- data/lib/generators/online_migrations/{background_migration_generator.rb → data_migration_generator.rb} +4 -4
- data/lib/generators/online_migrations/templates/change_background_data_migrations.rb.tt +34 -0
- data/lib/generators/online_migrations/templates/{background_data_migration.rb.tt → data_migration.rb.tt} +8 -9
- data/lib/generators/online_migrations/templates/initializer.rb.tt +19 -25
- data/lib/generators/online_migrations/templates/install_migration.rb.tt +9 -40
- data/lib/generators/online_migrations/upgrade_generator.rb +16 -8
- data/lib/online_migrations/active_record_batch_enumerator.rb +8 -0
- data/lib/online_migrations/background_data_migrations/backfill_column.rb +50 -0
- data/lib/online_migrations/background_data_migrations/config.rb +62 -0
- data/lib/online_migrations/{background_migrations → background_data_migrations}/copy_column.rb +15 -28
- data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_associated_records.rb +9 -5
- data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_orphaned_records.rb +5 -9
- data/lib/online_migrations/background_data_migrations/migration.rb +312 -0
- data/lib/online_migrations/{background_migrations → background_data_migrations}/migration_helpers.rb +72 -61
- data/lib/online_migrations/background_data_migrations/migration_job.rb +158 -0
- data/lib/online_migrations/background_data_migrations/migration_status_validator.rb +65 -0
- data/lib/online_migrations/{background_migrations → background_data_migrations}/perform_action_on_relation.rb +5 -5
- data/lib/online_migrations/{background_migrations → background_data_migrations}/reset_counters.rb +5 -5
- data/lib/online_migrations/background_data_migrations/scheduler.rb +78 -0
- data/lib/online_migrations/background_data_migrations/ticker.rb +62 -0
- data/lib/online_migrations/background_schema_migrations/config.rb +2 -2
- data/lib/online_migrations/background_schema_migrations/migration.rb +51 -123
- data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +25 -46
- data/lib/online_migrations/background_schema_migrations/migration_runner.rb +43 -97
- data/lib/online_migrations/background_schema_migrations/scheduler.rb +2 -2
- data/lib/online_migrations/change_column_type_helpers.rb +3 -2
- data/lib/online_migrations/config.rb +4 -4
- data/lib/online_migrations/data_migration.rb +127 -0
- data/lib/online_migrations/lock_retrier.rb +5 -2
- data/lib/online_migrations/schema_statements.rb +1 -1
- data/lib/online_migrations/shard_aware.rb +44 -0
- data/lib/online_migrations/version.rb +1 -1
- data/lib/online_migrations.rb +18 -11
- metadata +22 -21
- data/lib/online_migrations/background_migration.rb +0 -64
- data/lib/online_migrations/background_migrations/backfill_column.rb +0 -54
- data/lib/online_migrations/background_migrations/background_migration_class_validator.rb +0 -29
- data/lib/online_migrations/background_migrations/config.rb +0 -74
- data/lib/online_migrations/background_migrations/migration.rb +0 -329
- data/lib/online_migrations/background_migrations/migration_job.rb +0 -109
- data/lib/online_migrations/background_migrations/migration_job_runner.rb +0 -66
- data/lib/online_migrations/background_migrations/migration_job_status_validator.rb +0 -29
- data/lib/online_migrations/background_migrations/migration_runner.rb +0 -161
- data/lib/online_migrations/background_migrations/migration_status_validator.rb +0 -48
- data/lib/online_migrations/background_migrations/scheduler.rb +0 -42
@@ -0,0 +1,312 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OnlineMigrations
|
4
|
+
module BackgroundDataMigrations
|
5
|
+
# Class representing background data migration.
|
6
|
+
#
|
7
|
+
# @note The records of this class should not be created manually, but via
|
8
|
+
# `enqueue_background_data_migration` helper inside migrations.
|
9
|
+
#
|
10
|
+
class Migration < ApplicationRecord
|
11
|
+
include ShardAware
|
12
|
+
|
13
|
+
STATUSES = [
|
14
|
+
"enqueued", # The migration has been enqueued by the user.
|
15
|
+
"running", # The migration is being performed by a migration executor.
|
16
|
+
"pausing", # The migration has been told to pause but is finishing work.
|
17
|
+
"paused", # The migration was paused in the middle of the run by the user.
|
18
|
+
"failed", # The migration raises an exception when running.
|
19
|
+
"succeeded", # The migration finished without error.
|
20
|
+
"cancelling", # The migration has been told to cancel but is finishing work.
|
21
|
+
"cancelled", # The migration was cancelled by the user.
|
22
|
+
]
|
23
|
+
|
24
|
+
COMPLETED_STATUSES = ["succeeded", "failed", "cancelled"]
|
25
|
+
|
26
|
+
ACTIVE_STATUSES = [
|
27
|
+
"enqueued",
|
28
|
+
"running",
|
29
|
+
"pausing",
|
30
|
+
"paused",
|
31
|
+
"cancelling",
|
32
|
+
]
|
33
|
+
|
34
|
+
STOPPING_STATUSES = ["pausing", "cancelling", "cancelled"]
|
35
|
+
|
36
|
+
self.table_name = :background_data_migrations
|
37
|
+
self.ignored_columns += ["parent_id", "batch_column_name", "min_value", "max_value", "rows_count",
|
38
|
+
"batch_size", "sub_batch_size", "batch_pause", "sub_batch_pause_ms", "composite"]
|
39
|
+
|
40
|
+
scope :queue_order, -> { order(created_at: :asc) }
|
41
|
+
scope :for_migration_name, ->(migration_name) { where(migration_name: normalize_migration_name(migration_name)) }
|
42
|
+
scope :for_configuration, ->(migration_name, arguments) do
|
43
|
+
for_migration_name(migration_name).where("arguments = ?", arguments.to_json)
|
44
|
+
end
|
45
|
+
|
46
|
+
alias_attribute :name, :migration_name
|
47
|
+
|
48
|
+
enum :status, STATUSES.index_with(&:to_s)
|
49
|
+
|
50
|
+
validates :migration_name, presence: true
|
51
|
+
validates :arguments, uniqueness: { scope: [:migration_name, :shard] }
|
52
|
+
|
53
|
+
validates_with MigrationStatusValidator, on: :update
|
54
|
+
|
55
|
+
before_save :set_defaults
|
56
|
+
after_save :instrument_status_change, if: :status_previously_changed?
|
57
|
+
|
58
|
+
# @private
|
59
|
+
def self.normalize_migration_name(migration_name)
|
60
|
+
namespace = ::OnlineMigrations.config.background_data_migrations.migrations_module
|
61
|
+
migration_name.sub(/^(::)?#{namespace}::/, "")
|
62
|
+
end
|
63
|
+
|
64
|
+
def migration_name=(class_name)
|
65
|
+
class_name = class_name.name if class_name.is_a?(Class)
|
66
|
+
write_attribute(:migration_name, self.class.normalize_migration_name(class_name))
|
67
|
+
end
|
68
|
+
alias name= migration_name=
|
69
|
+
|
70
|
+
# Returns whether the migration has been started, which is indicated by the
|
71
|
+
# started_at timestamp being present.
|
72
|
+
#
|
73
|
+
# @return [Boolean] whether the migration was started.
|
74
|
+
#
|
75
|
+
def started?
|
76
|
+
started_at.present?
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns whether the migration is completed, which is defined as
|
80
|
+
# having a status of succeeded, failed, or cancelled.
|
81
|
+
#
|
82
|
+
# @return [Boolean] whether the migration is completed.
|
83
|
+
#
|
84
|
+
def completed?
|
85
|
+
COMPLETED_STATUSES.include?(status)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns whether the migration is active, which is defined as
|
89
|
+
# having a status of enqueued, running, pausing, paused, or cancelling.
|
90
|
+
#
|
91
|
+
# @return [Boolean] whether the migration is active.
|
92
|
+
#
|
93
|
+
def active?
|
94
|
+
ACTIVE_STATUSES.include?(status)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns whether the migration is stopping, which is defined as having a status
|
98
|
+
# of pausing or cancelling. The status of cancelled is also considered
|
99
|
+
# stopping since a migration can be cancelled while its job still exists in the
|
100
|
+
# queue, and we want to handle it the same way as a cancelling run.
|
101
|
+
#
|
102
|
+
# @return [Boolean] whether the migration is stopping.
|
103
|
+
#
|
104
|
+
def stopping?
|
105
|
+
STOPPING_STATUSES.include?(status)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns whether a migration is stuck, which is defined as having a status of
|
109
|
+
# cancelling or pausing, and not having been updated in the last 5 minutes.
|
110
|
+
#
|
111
|
+
# @return [Boolean] whether the migration is stuck.
|
112
|
+
#
|
113
|
+
def stuck?
|
114
|
+
stuck_timeout = OnlineMigrations.config.background_data_migrations.stuck_timeout
|
115
|
+
(cancelling? || pausing?) && updated_at <= stuck_timeout.ago
|
116
|
+
end
|
117
|
+
|
118
|
+
# @private
|
119
|
+
def start
|
120
|
+
if running? && !started?
|
121
|
+
update!(started_at: Time.current)
|
122
|
+
data_migration.after_start
|
123
|
+
true
|
124
|
+
elsif enqueued?
|
125
|
+
update!(status: :running, started_at: Time.current)
|
126
|
+
data_migration.after_start
|
127
|
+
true
|
128
|
+
else
|
129
|
+
false
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Cancel this data migration. No-op if migration is completed.
|
134
|
+
#
|
135
|
+
# @return [Boolean] whether this data migration was cancelled.
|
136
|
+
#
|
137
|
+
def cancel
|
138
|
+
return false if completed?
|
139
|
+
|
140
|
+
if paused? || stuck?
|
141
|
+
update!(status: :cancelled, finished_at: Time.current)
|
142
|
+
elsif enqueued?
|
143
|
+
cancelled!
|
144
|
+
else
|
145
|
+
cancelling!
|
146
|
+
end
|
147
|
+
|
148
|
+
true
|
149
|
+
end
|
150
|
+
|
151
|
+
# Pause this data migration. No-op if migration is completed.
|
152
|
+
#
|
153
|
+
# @return [Boolean] whether this data migration was paused.
|
154
|
+
#
|
155
|
+
def pause
|
156
|
+
return false if completed?
|
157
|
+
|
158
|
+
if enqueued? || stuck?
|
159
|
+
paused!
|
160
|
+
else
|
161
|
+
pausing!
|
162
|
+
end
|
163
|
+
|
164
|
+
true
|
165
|
+
end
|
166
|
+
|
167
|
+
# Resume this data migration. No-op if migration is not paused.
|
168
|
+
#
|
169
|
+
# @return [Boolean] whether this data migration was resumed.
|
170
|
+
#
|
171
|
+
def resume
|
172
|
+
if paused?
|
173
|
+
enqueued!
|
174
|
+
true
|
175
|
+
else
|
176
|
+
false
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# @private
|
181
|
+
def stop
|
182
|
+
return false if completed?
|
183
|
+
|
184
|
+
if cancelling?
|
185
|
+
update!(status: :cancelled, finished_at: Time.current)
|
186
|
+
data_migration.after_cancel
|
187
|
+
data_migration.after_complete
|
188
|
+
elsif pausing?
|
189
|
+
paused!
|
190
|
+
data_migration.after_pause
|
191
|
+
end
|
192
|
+
|
193
|
+
data_migration.after_stop
|
194
|
+
true
|
195
|
+
end
|
196
|
+
|
197
|
+
# @private
|
198
|
+
def complete
|
199
|
+
return false if completed?
|
200
|
+
|
201
|
+
if running?
|
202
|
+
update!(status: :succeeded, finished_at: Time.current)
|
203
|
+
data_migration.after_complete
|
204
|
+
elsif pausing?
|
205
|
+
update!(status: :paused, finished_at: Time.current)
|
206
|
+
elsif cancelling?
|
207
|
+
update!(status: :cancelled, finished_at: Time.current)
|
208
|
+
data_migration.after_complete
|
209
|
+
end
|
210
|
+
|
211
|
+
true
|
212
|
+
end
|
213
|
+
|
214
|
+
# @private
|
215
|
+
def persist_progress(cursor, number_of_ticks, duration)
|
216
|
+
update!(
|
217
|
+
cursor: cursor,
|
218
|
+
tick_count: tick_count + number_of_ticks,
|
219
|
+
time_running: time_running + duration
|
220
|
+
)
|
221
|
+
end
|
222
|
+
|
223
|
+
# @private
|
224
|
+
def persist_error(error)
|
225
|
+
backtrace = error.backtrace
|
226
|
+
backtrace_cleaner = OnlineMigrations.config.backtrace_cleaner
|
227
|
+
backtrace = backtrace_cleaner.clean(backtrace) if backtrace_cleaner
|
228
|
+
|
229
|
+
update!(
|
230
|
+
status: :failed,
|
231
|
+
finished_at: Time.current,
|
232
|
+
error_class: error.class.name,
|
233
|
+
error_message: error.message,
|
234
|
+
backtrace: backtrace
|
235
|
+
)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Returns whether this migration is pausable.
|
239
|
+
#
|
240
|
+
def pausable?
|
241
|
+
true
|
242
|
+
end
|
243
|
+
|
244
|
+
# Returns the progress of the data migration.
|
245
|
+
#
|
246
|
+
# @return [Float, nil]
|
247
|
+
# - when background migration is configured to not track progress, returns `nil`
|
248
|
+
# - otherwise returns value in range from 0.0 to 100.0
|
249
|
+
#
|
250
|
+
def progress
|
251
|
+
if succeeded?
|
252
|
+
100.0
|
253
|
+
elsif enqueued? || tick_total == 0
|
254
|
+
0.0
|
255
|
+
elsif tick_total
|
256
|
+
([tick_count.to_f / tick_total, 1.0].min * 100)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# Returns data migration associated with this migration.
|
261
|
+
#
|
262
|
+
# @return [OnlineMigrations::DataMigration]
|
263
|
+
#
|
264
|
+
def data_migration
|
265
|
+
@data_migration ||= begin
|
266
|
+
klass = DataMigration.named(migration_name)
|
267
|
+
klass.new(*arguments)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Mark this migration as ready to be processed again.
|
272
|
+
#
|
273
|
+
# This method marks failed migrations as ready to be processed again, and
|
274
|
+
# they will be picked up on the next Scheduler run.
|
275
|
+
#
|
276
|
+
def retry
|
277
|
+
if failed?
|
278
|
+
update!(
|
279
|
+
status: :enqueued,
|
280
|
+
started_at: nil,
|
281
|
+
finished_at: nil,
|
282
|
+
error_class: nil,
|
283
|
+
error_message: nil,
|
284
|
+
backtrace: nil,
|
285
|
+
jid: nil
|
286
|
+
)
|
287
|
+
true
|
288
|
+
else
|
289
|
+
false
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
private
|
294
|
+
def set_defaults
|
295
|
+
config = ::OnlineMigrations.config.background_data_migrations
|
296
|
+
self.max_attempts ||= config.max_attempts
|
297
|
+
self.tick_total ||= on_shard_if_present do
|
298
|
+
data_migration.count
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def instrument_status_change
|
303
|
+
payload = { migration: self }
|
304
|
+
if running?
|
305
|
+
ActiveSupport::Notifications.instrument("started.background_data_migrations", payload)
|
306
|
+
elsif succeeded?
|
307
|
+
ActiveSupport::Notifications.instrument("completed.background_data_migrations", payload)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
data/lib/online_migrations/{background_migrations → background_data_migrations}/migration_helpers.rb
RENAMED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module OnlineMigrations
|
4
|
-
module
|
4
|
+
module BackgroundDataMigrations
|
5
5
|
module MigrationHelpers
|
6
6
|
# Backfills column data using background migrations.
|
7
7
|
#
|
@@ -13,7 +13,7 @@ module OnlineMigrations
|
|
13
13
|
# @param options [Hash] used to control the behavior of background migration.
|
14
14
|
# See `#enqueue_background_data_migration`
|
15
15
|
#
|
16
|
-
# @return [OnlineMigrations::
|
16
|
+
# @return [OnlineMigrations::BackgroundDataMigrations::Migration]
|
17
17
|
#
|
18
18
|
# @example
|
19
19
|
# backfill_column_in_background(:users, :admin, false)
|
@@ -69,7 +69,7 @@ module OnlineMigrations
|
|
69
69
|
# @param options [Hash] used to control the behavior of background migration.
|
70
70
|
# See `#enqueue_background_data_migration`
|
71
71
|
#
|
72
|
-
# @return [OnlineMigrations::
|
72
|
+
# @return [OnlineMigrations::BackgroundDataMigrations::Migration]
|
73
73
|
#
|
74
74
|
# @example
|
75
75
|
# backfill_column_for_type_change_in_background(:files, :size)
|
@@ -108,7 +108,14 @@ module OnlineMigrations
|
|
108
108
|
end
|
109
109
|
|
110
110
|
tmp_columns = column_names.map { |column_name| "#{column_name}_for_type_change" }
|
111
|
-
|
111
|
+
|
112
|
+
if model_name
|
113
|
+
model_name = model_name.name if model_name.is_a?(Class)
|
114
|
+
connection_class_name = Utils.find_connection_class(model_name.constantize).name
|
115
|
+
end
|
116
|
+
|
117
|
+
# model_name = model_name.name if model_name.is_a?(Class)
|
118
|
+
# connection_class = Utils.find_connection_class(model_name.constantize) if model_name
|
112
119
|
|
113
120
|
enqueue_background_data_migration(
|
114
121
|
"CopyColumn",
|
@@ -117,6 +124,7 @@ module OnlineMigrations
|
|
117
124
|
tmp_columns,
|
118
125
|
model_name,
|
119
126
|
type_cast_functions,
|
127
|
+
connection_class_name: connection_class_name,
|
120
128
|
**options
|
121
129
|
)
|
122
130
|
end
|
@@ -134,7 +142,7 @@ module OnlineMigrations
|
|
134
142
|
# @param options [Hash] used to control the behavior of background migration.
|
135
143
|
# See `#enqueue_background_data_migration`
|
136
144
|
#
|
137
|
-
# @return [OnlineMigrations::
|
145
|
+
# @return [OnlineMigrations::BackgroundDataMigrations::Migration]
|
138
146
|
#
|
139
147
|
# @example
|
140
148
|
# copy_column_in_background(:users, :id, :id_for_type_change)
|
@@ -189,7 +197,7 @@ module OnlineMigrations
|
|
189
197
|
# @param options [Hash] used to control the behavior of background migration.
|
190
198
|
# See `#enqueue_background_data_migration`
|
191
199
|
#
|
192
|
-
# @return [OnlineMigrations::
|
200
|
+
# @return [OnlineMigrations::BackgroundDataMigrations::Migration]
|
193
201
|
#
|
194
202
|
# @example
|
195
203
|
# reset_counters_in_background("User", :projects, :friends, touch: true)
|
@@ -226,7 +234,7 @@ module OnlineMigrations
|
|
226
234
|
# @param options [Hash] used to control the behavior of background migration.
|
227
235
|
# See `#enqueue_background_data_migration`
|
228
236
|
#
|
229
|
-
# @return [OnlineMigrations::
|
237
|
+
# @return [OnlineMigrations::BackgroundDataMigrations::Migration]
|
230
238
|
#
|
231
239
|
# @example
|
232
240
|
# delete_orphaned_records_in_background("Post", :author)
|
@@ -255,7 +263,7 @@ module OnlineMigrations
|
|
255
263
|
# @param options [Hash] used to control the behavior of background migration.
|
256
264
|
# See `#enqueue_background_data_migration`
|
257
265
|
#
|
258
|
-
# @return [OnlineMigrations::
|
266
|
+
# @return [OnlineMigrations::BackgroundDataMigrations::Migration]
|
259
267
|
#
|
260
268
|
# @example
|
261
269
|
# delete_associated_records_in_background("Link", 1, :clicks)
|
@@ -286,7 +294,7 @@ module OnlineMigrations
|
|
286
294
|
# @param options [Hash] used to control the behavior of background migration.
|
287
295
|
# See `#enqueue_background_data_migration`
|
288
296
|
#
|
289
|
-
# @return [OnlineMigrations::
|
297
|
+
# @return [OnlineMigrations::BackgroundDataMigrations::Migration]
|
290
298
|
#
|
291
299
|
# @example Delete records
|
292
300
|
# perform_action_on_relation_in_background("User", { banned: true }, :delete_all)
|
@@ -328,56 +336,64 @@ module OnlineMigrations
|
|
328
336
|
# based on the current migration settings and the previous batch bounds. Each job's execution status
|
329
337
|
# is tracked in the database as the migration runs.
|
330
338
|
#
|
331
|
-
# @param migration_name [String, Class] Background migration
|
332
|
-
# @param arguments [Array] Extra arguments to pass to the
|
333
|
-
# @option options [
|
334
|
-
# @option options [
|
335
|
-
#
|
336
|
-
# @
|
337
|
-
# defaults to `SELECT MAX(batch_column_name)`
|
338
|
-
# @option options [Integer] :batch_size (1_000) Number of rows to process in a single background migration run
|
339
|
-
# @option options [Integer] :sub_batch_size (100) Smaller batches size that the batches will be divided into
|
340
|
-
# @option options [Integer] :batch_pause (0) Pause interval between each background migration job's execution (in seconds)
|
341
|
-
# @option options [Integer] :sub_batch_pause_ms (100) Number of milliseconds to sleep between each sub_batch execution
|
342
|
-
# @option options [Integer] :batch_max_attempts (5) Maximum number of batch run attempts
|
343
|
-
#
|
344
|
-
# @return [OnlineMigrations::BackgroundMigrations::Migration]
|
339
|
+
# @param migration_name [String, Class] Background migration class name
|
340
|
+
# @param arguments [Array] Extra arguments to pass to the migration instance when the migration runs
|
341
|
+
# @option options [Integer] :max_attempts (5) Maximum number of batch run attempts
|
342
|
+
# @option options [String, nil] :connection_class_name Class name to use to get connections
|
343
|
+
#
|
344
|
+
# @return [OnlineMigrations::BackgroundDataMigrations::Migration]
|
345
345
|
#
|
346
346
|
# @example
|
347
|
-
# enqueue_background_data_migration("BackfillProjectIssuesCount"
|
348
|
-
# batch_size: 10_000, batch_max_attempts: 10)
|
347
|
+
# enqueue_background_data_migration("BackfillProjectIssuesCount")
|
349
348
|
#
|
350
349
|
# # Given the background migration exists:
|
351
350
|
#
|
352
|
-
# class BackfillProjectIssuesCount < OnlineMigrations::
|
353
|
-
# def
|
354
|
-
# Project.
|
351
|
+
# class BackfillProjectIssuesCount < OnlineMigrations::DataMigration
|
352
|
+
# def collection
|
353
|
+
# Project.in_batches(of: 100)
|
355
354
|
# end
|
356
355
|
#
|
357
|
-
# def
|
356
|
+
# def process(projects)
|
358
357
|
# projects.update_all(
|
359
358
|
# "issues_count = (SELECT COUNT(*) FROM issues WHERE issues.project_id = projects.id)"
|
360
359
|
# )
|
361
360
|
# end
|
362
361
|
#
|
363
|
-
# # To be able to track progress, you need to define this method
|
362
|
+
# # To be able to track progress, you need to define this method.
|
364
363
|
# def count
|
365
364
|
# Project.maximum(:id)
|
366
365
|
# end
|
367
366
|
# end
|
368
367
|
#
|
369
|
-
# @note For convenience, the enqueued background migration is run inline
|
368
|
+
# @note For convenience, the enqueued background data migration is run inline
|
370
369
|
# in development and test environments
|
371
370
|
#
|
372
371
|
def enqueue_background_data_migration(migration_name, *arguments, **options)
|
373
|
-
|
372
|
+
options.assert_valid_keys(:max_attempts, :connection_class_name)
|
373
|
+
|
374
|
+
migration_name = migration_name.name if migration_name.is_a?(Class)
|
375
|
+
options[:connection_class_name] ||= compute_connection_class_name(migration_name, arguments)
|
374
376
|
|
375
|
-
if Utils.
|
376
|
-
|
377
|
-
runner.run_all_migration_jobs
|
377
|
+
if Utils.multiple_databases? && !options[:connection_class_name]
|
378
|
+
raise ArgumentError, "You must pass a :connection_class_name when using multiple databases."
|
378
379
|
end
|
379
380
|
|
380
|
-
|
381
|
+
connection_class = options[:connection_class_name].constantize
|
382
|
+
shards = Utils.shard_names(connection_class)
|
383
|
+
shards = [nil] if shards.size == 1
|
384
|
+
|
385
|
+
shards.each do |shard|
|
386
|
+
# Can't use `find_or_create_by` or hash syntax here, because it does not correctly work with json `arguments`.
|
387
|
+
migration = Migration.where(migration_name: migration_name, shard: shard).where("arguments = ?", arguments.to_json).first
|
388
|
+
migration ||= Migration.create!(**options, migration_name: migration_name, arguments: arguments, shard: shard)
|
389
|
+
|
390
|
+
if Utils.run_background_migrations_inline?
|
391
|
+
job = OnlineMigrations.config.background_data_migrations.job
|
392
|
+
job.constantize.perform_inline(migration.id)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
true
|
381
397
|
end
|
382
398
|
alias enqueue_background_migration enqueue_background_data_migration
|
383
399
|
|
@@ -418,45 +434,40 @@ module OnlineMigrations
|
|
418
434
|
|
419
435
|
if arguments
|
420
436
|
arguments = Array(arguments)
|
421
|
-
|
437
|
+
migrations = Migration.for_configuration(migration_name, arguments).to_a
|
422
438
|
configuration[:arguments] = arguments.to_json
|
423
439
|
else
|
424
|
-
|
440
|
+
migrations = Migration.for_migration_name(migration_name).to_a
|
425
441
|
end
|
426
442
|
|
427
|
-
if
|
428
|
-
Utils.raise_in_prod_or_say_in_dev("Could not find background data migration for the given configuration: #{configuration}")
|
429
|
-
elsif !
|
430
|
-
raise "Expected background data migration for the given configuration to be marked as 'succeeded'
|
431
|
-
"but it is '#{migration.status}': #{configuration}"
|
443
|
+
if migrations.empty?
|
444
|
+
Utils.raise_in_prod_or_say_in_dev("Could not find background data migration(s) for the given configuration: #{configuration}.")
|
445
|
+
elsif !migrations.all?(&:succeeded?)
|
446
|
+
raise "Expected background data migration(s) for the given configuration to be marked as 'succeeded': #{configuration}."
|
432
447
|
end
|
433
448
|
end
|
434
449
|
alias ensure_background_migration_succeeded ensure_background_data_migration_succeeded
|
435
450
|
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
451
|
+
private
|
452
|
+
def compute_connection_class_name(migration_name, arguments)
|
453
|
+
klass = DataMigration.named(migration_name)
|
454
|
+
data_migration = klass.new(*arguments)
|
440
455
|
|
441
|
-
|
456
|
+
collection = data_migration.collection
|
442
457
|
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
Migration.create!(**options, migration_name: migration_name, arguments: arguments, shard: nil) do |migration|
|
448
|
-
shards = Utils.shard_names(migration.migration_model)
|
449
|
-
if shards.size > 1
|
450
|
-
migration.children = shards.map do |shard|
|
451
|
-
child = migration.dup
|
452
|
-
child.shard = shard
|
453
|
-
child
|
458
|
+
model =
|
459
|
+
case collection
|
460
|
+
when ActiveRecord::Relation then collection.model
|
461
|
+
when ActiveRecord::Batches::BatchEnumerator then collection.relation.model
|
454
462
|
end
|
455
463
|
|
456
|
-
|
464
|
+
if model
|
465
|
+
connection_class = Utils.find_connection_class(model)
|
466
|
+
connection_class.name
|
457
467
|
end
|
468
|
+
rescue NotImplementedError
|
469
|
+
nil
|
458
470
|
end
|
459
|
-
end
|
460
471
|
end
|
461
472
|
end
|
462
473
|
end
|