online_migrations 0.26.0 → 0.27.1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/docs/0.27-upgrade.md +24 -0
  4. data/docs/background_data_migrations.md +200 -101
  5. data/docs/background_schema_migrations.md +2 -2
  6. data/lib/generators/online_migrations/{background_migration_generator.rb → data_migration_generator.rb} +4 -4
  7. data/lib/generators/online_migrations/templates/change_background_data_migrations.rb.tt +34 -0
  8. data/lib/generators/online_migrations/templates/{background_data_migration.rb.tt → data_migration.rb.tt} +8 -9
  9. data/lib/generators/online_migrations/templates/initializer.rb.tt +19 -25
  10. data/lib/generators/online_migrations/templates/install_migration.rb.tt +9 -40
  11. data/lib/generators/online_migrations/upgrade_generator.rb +16 -8
  12. data/lib/online_migrations/active_record_batch_enumerator.rb +8 -0
  13. data/lib/online_migrations/background_data_migrations/backfill_column.rb +50 -0
  14. data/lib/online_migrations/background_data_migrations/config.rb +62 -0
  15. data/lib/online_migrations/{background_migrations → background_data_migrations}/copy_column.rb +15 -28
  16. data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_associated_records.rb +9 -5
  17. data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_orphaned_records.rb +5 -9
  18. data/lib/online_migrations/background_data_migrations/migration.rb +312 -0
  19. data/lib/online_migrations/{background_migrations → background_data_migrations}/migration_helpers.rb +72 -61
  20. data/lib/online_migrations/background_data_migrations/migration_job.rb +160 -0
  21. data/lib/online_migrations/background_data_migrations/migration_status_validator.rb +65 -0
  22. data/lib/online_migrations/{background_migrations → background_data_migrations}/perform_action_on_relation.rb +5 -5
  23. data/lib/online_migrations/{background_migrations → background_data_migrations}/reset_counters.rb +5 -5
  24. data/lib/online_migrations/background_data_migrations/scheduler.rb +78 -0
  25. data/lib/online_migrations/background_data_migrations/ticker.rb +62 -0
  26. data/lib/online_migrations/background_schema_migrations/config.rb +2 -2
  27. data/lib/online_migrations/background_schema_migrations/migration.rb +51 -123
  28. data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +25 -46
  29. data/lib/online_migrations/background_schema_migrations/migration_runner.rb +43 -97
  30. data/lib/online_migrations/background_schema_migrations/scheduler.rb +2 -2
  31. data/lib/online_migrations/change_column_type_helpers.rb +17 -4
  32. data/lib/online_migrations/config.rb +4 -4
  33. data/lib/online_migrations/data_migration.rb +127 -0
  34. data/lib/online_migrations/error_messages.rb +2 -0
  35. data/lib/online_migrations/lock_retrier.rb +5 -2
  36. data/lib/online_migrations/schema_statements.rb +1 -1
  37. data/lib/online_migrations/shard_aware.rb +44 -0
  38. data/lib/online_migrations/version.rb +1 -1
  39. data/lib/online_migrations.rb +18 -11
  40. metadata +22 -21
  41. data/lib/online_migrations/background_migration.rb +0 -64
  42. data/lib/online_migrations/background_migrations/backfill_column.rb +0 -54
  43. data/lib/online_migrations/background_migrations/background_migration_class_validator.rb +0 -29
  44. data/lib/online_migrations/background_migrations/config.rb +0 -74
  45. data/lib/online_migrations/background_migrations/migration.rb +0 -329
  46. data/lib/online_migrations/background_migrations/migration_job.rb +0 -109
  47. data/lib/online_migrations/background_migrations/migration_job_runner.rb +0 -66
  48. data/lib/online_migrations/background_migrations/migration_job_status_validator.rb +0 -29
  49. data/lib/online_migrations/background_migrations/migration_runner.rb +0 -161
  50. data/lib/online_migrations/background_migrations/migration_status_validator.rb +0 -48
  51. data/lib/online_migrations/background_migrations/scheduler.rb +0 -42
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbca384d5eaf4ab575e80c60160d42326ef86f1baf21419f290663e0d9698eb0
4
- data.tar.gz: b90abf4e278ee1b2ab27e60472f6bb127b124797084f53205a0929a5cc98268b
3
+ metadata.gz: 8a3658cc979ead6b8dea71140f3ba95b0f7a4a4c26a27891f49a5b6925c94e59
4
+ data.tar.gz: 7d3ab5e09422e28078543ac67fa0ecae1a7afe9d8e29f67e55d6b772fb708e50
5
5
  SHA512:
6
- metadata.gz: eea056dd2b0c5a24213543d96d6b2ca5a1c230c38aa37f475d674157bc354a8d0c1cb39e6e2c0a6e86867b184e82b51ec9ad0452eed00e1a7b7a6098b203c3ff
7
- data.tar.gz: 9636d4b788d655ad04d0141cd28ae61c053b15d83f00c63b236ee181feeb438c18f2a8f34da49b26d955ff3c004c053f0d570d00ff6801e47e15c438b09f2c51
6
+ metadata.gz: 43ae3f3ebffa5491d4ac9b99b987b048702b9142e9a4ae49182b705b9c81a562acbea6dc1580ef76e49a9124adf2d662723451b6d03003f10f05c1d39ff4a33e
7
+ data.tar.gz: 5793df9ffd71454f2edb251d079439c5dfb0cfe212a30360654cbb5622c23fcd47a63e2a8cdd6b8d4df2bd7d3b39ec4d29eede4a99519264075e65956bdd1b55
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  ## master (unreleased)
2
2
 
3
+ ## 0.27.1 (2025-05-08)
4
+
5
+ - Fix background data migrations to enumerate using the correct shard
6
+
7
+ ## 0.27.0 (2025-05-01)
8
+
9
+ - **WARNING**: This release has breaking changes! See `docs/0.27-upgrade.md` for release notes
10
+
11
+ - Add ability to run background data migrations in parallel
12
+
13
+ ```ruby
14
+ # Run 2 data migrations in parallel.
15
+ OnlineMigrations.run_background_data_migrations(concurrency: 2)
16
+ ```
17
+
18
+ - Retry deadlocks by lock retrier
19
+ - Fix copying check constraints on columns with similar names when changing column type
20
+
3
21
  ## 0.26.0 (2025-04-28)
4
22
 
5
23
  - Drop support for Ruby < 3.1 and Rails < 7.1
@@ -0,0 +1,24 @@
1
+ # Upgrading to online_migrations 0.27.0
2
+
3
+ In this version, background data migrations internals were significantly refactored and rewritten, that allowed to make the gem much simpler and its API more flexible and not attached to a single use case (whole table data migrations). It relies on [Sidekiq's Iteration feature](https://github.com/sidekiq/sidekiq/wiki/Iteration), so having Sidekiq 7.3.3+ is a hard requirement for background data migrations feature to work now.
4
+
5
+ This is one of the preceding releases before v1.0.
6
+
7
+ To upgrade:
8
+
9
+ * Upgrade gem to v0.27: `gem 'online_migrations', '~> 0.27.0'`
10
+ * Upgrade the gem's initializer in `config/online_migrations.rb` by referring to the [newest contents](https://github.com/fatkodima/online_migrations/blob/master/lib/generators/online_migrations/templates/initializer.rb.tt)
11
+
12
+ If you don't use any of the [background data migrations](background_data_migrations.md) or [background schema migrations](background_schema_migrations.md), then this is probably all you need.
13
+
14
+ If you use background data migrations:
15
+
16
+ * Make sure all existing background data migrations completed before upgrading
17
+
18
+ * Get the latest schema changes
19
+ ```sh
20
+ $ bin/rails generate online_migrations:upgrade
21
+ $ bin/rails db:migrate
22
+ ```
23
+
24
+ Look at [background data migrations guide](background_data_migrations.md) to find the API changes and new features.
@@ -2,10 +2,14 @@
2
2
 
3
3
  When a project grows, your database starts to be heavy and changing the data through the deployment process can be very painful.
4
4
 
5
- Background migrations should be used to perform data migrations on large tables or when the migration will take a lot of time. For example, you can use background migrations to migrate data that’s stored in a single JSON column to a separate table instead or backfill some column's value from an API.
5
+ Background data migrations should be used to perform data migrations on large tables or when the migration will take a lot of time. For example, you can use background data migrations to migrate data that’s stored in a single JSON column to a separate table instead or backfill some column's value from an API.
6
6
 
7
7
  **Note**: You probably don't need to use background migrations for smaller projects, since updating data directly on smaller databases will be perfectly fine and will not block the deployment too much.
8
8
 
9
+ ## Requirements
10
+
11
+ Data migrations uses [sidekiq iterable job](https://github.com/sidekiq/sidekiq/wiki/Iteration) under the hood and so requires `sidekiq` 7.3.3+ to work.
12
+
9
13
  ## Installation
10
14
 
11
15
  Make sure you have migration files generated when installed this gem:
@@ -14,7 +18,7 @@ Make sure you have migration files generated when installed this gem:
14
18
  $ bin/rails generate online_migrations:install
15
19
  ```
16
20
 
17
- Start a background migrations scheduler. For example, to run it on cron using [whenever gem](https://github.com/javan/whenever) add the following lines to its `schedule.rb` file:
21
+ Start a background data migrations scheduler. For example, to run it on cron using [whenever gem](https://github.com/javan/whenever) add the following lines to its `schedule.rb` file:
18
22
 
19
23
  ```ruby
20
24
  every 1.minute do
@@ -22,40 +26,39 @@ every 1.minute do
22
26
  end
23
27
  ```
24
28
 
25
- ## Creating a Background Migration
29
+ ## Creating a Data Migration
26
30
 
27
- A generator is provided to create background migrations. Generate a new background migration by running:
31
+ A generator is provided to create data migrations. Generate a new data migration by running:
28
32
 
29
33
  ```bash
30
- $ bin/rails generate online_migrations:background_migration backfill_project_issues_count
34
+ $ bin/rails generate online_migrations:data_migration backfill_project_issues_count
31
35
  ```
32
36
 
33
- This creates the background migration file `lib/online_migrations/background_migrations/backfill_project_issues_count.rb`
34
- and the regular migration file `db/migrate/xxxxxxxxxxxxxx_enqueue_backfill_project_issues_count.rb` where we enqueue it.
37
+ This creates a data migration file `lib/online_migrations/data_migrations/backfill_project_issues_count.rb`
38
+ and a regular migration file `db/migrate/xxxxxxxxxxxxxx_enqueue_backfill_project_issues_count.rb` where it is enqueued.
35
39
 
36
- The generated class is a subclass of `OnlineMigrations::BackgroundMigration` that implements:
40
+ The generated class is a subclass of `OnlineMigrations::DataMigration` that implements:
37
41
 
38
- * `relation`: return an `ActiveRecord::Relation` to be iterated over
39
- * `process_batch`: do the work of your background migration on a batch (`ActiveRecord::Relation`)
40
- * `count`: return the number of rows that will be iterated over (optional, to be
41
- able to show progress)
42
+ * `collection`: return a collection to be processed. Can be any of `ActiveRecord::Relation`, `ActiveRecord::Batches::BatchEnumerator`, `Array`, or `Enumerator`
43
+ * `process`: the action to be performed on each item from the `collection`
44
+ * `count`: return total count of iterations to be performed (optional, to be able to show progress)
42
45
 
43
46
  Example:
44
47
 
45
48
  ```ruby
46
- # lib/online_migrations/background_migrations/backfill_project_issues_count.rb
49
+ # lib/online_migrations/data_migrations/backfill_project_issues_count.rb
47
50
 
48
51
  module OnlineMigrations
49
- module BackgroundMigrations
50
- class BackfillProjectIssuesCount < OnlineMigrations::BackgroundMigration
52
+ module DataMigrations
53
+ class BackfillProjectIssuesCount < OnlineMigrations::DataMigration
51
54
  class Project < ActiveRecord::Base; end
52
55
 
53
- def relation
54
- Project.all
56
+ def collection
57
+ Project.in_batches(of: 100)
55
58
  end
56
59
 
57
- def process_batch(projects)
58
- projects.update_all(<<~SQL)
60
+ def process(relation)
61
+ relation.update_all(<<~SQL)
59
62
  issues_count = (
60
63
  SELECT COUNT(*)
61
64
  FROM issues
@@ -65,16 +68,69 @@ module OnlineMigrations
65
68
  end
66
69
 
67
70
  def count
68
- relation.count
71
+ collection.count
72
+ end
73
+ end
74
+ end
75
+ end
76
+ ```
77
+
78
+ ### Data Migrations with Custom Enumerators
79
+
80
+ If you have a special use case requiring iteration over an unsupported collection type,
81
+ such as external resources fetched from some API, you can implement the `build_enumerator(cursor:)`
82
+ method in your data migration.
83
+
84
+ This method should return an `Enumerator`, yielding pairs of `[item, cursor]`. Online Migrations
85
+ takes care of persisting the current cursor position and will provide it as the `cursor` argument
86
+ if your data migration is interrupted or resumed. The `cursor` is stored as a `String`,
87
+ so your custom enumerator should handle serializing/deserializing the value if required.
88
+
89
+ ```ruby
90
+ # lib/online_migrations/data_migrations/custom_enumerator_migration.rb
91
+
92
+ module OnlineMigrations
93
+ module DataMigrations
94
+ class CustomEnumeratorMigration < OnlineMigrations::DataMigration
95
+ def build_enumerator(cursor:)
96
+ after_id = cursor&.to_i
97
+ PostAPI.index(after_id: after_id).map { |post| [post, post.id] }.to_enum
98
+ end
99
+
100
+ def process(post)
101
+ Post.create!(post)
69
102
  end
70
103
  end
71
104
  end
72
105
  end
73
106
  ```
74
107
 
75
- ## Enqueueing a Background Migration
108
+ ### Customizing the Batch Size
76
109
 
77
- You can enqueue your background migration to be run by the scheduler via:
110
+ When processing records from an `ActiveRecord::Relation`, records are fetched in batches internally, and then each record is passed to the `#process` method.
111
+ The gem will query the database to fetch records in batches of 100 by default, but the batch size can be modified using the `collection_batch_size` macro:
112
+
113
+ ```ruby
114
+ module OnlineMigrations
115
+ module DataMigrations
116
+ class UpdatePostsMigration < OnlineMigrations::DataMigration
117
+ # Fetch records in batches of 1000
118
+ collection_batch_size(1000)
119
+
120
+ def collection
121
+ Post.all
122
+ end
123
+
124
+ def process(post)
125
+ post.update!(content: "New content!")
126
+ end
127
+ end
128
+ end
129
+ ```
130
+
131
+ ## Enqueueing a Data Migration
132
+
133
+ You can enqueue a data migration to be run by the scheduler via:
78
134
 
79
135
  ```ruby
80
136
  # db/migrate/xxxxxxxxxxxxxx_enqueue_backfill_project_issues_count.rb
@@ -89,20 +145,20 @@ class EnqueueBackfillProjectIssuesCount < ActiveRecord::Migration[8.0]
89
145
  end
90
146
  ```
91
147
 
92
- `enqueue_background_data_migration` accepts additional configuration options which controls how the background migration is run. Check the [source code](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/background_migrations/migration_helpers.rb) for the list of all available configuration options.
148
+ `enqueue_background_data_migration` accepts additional configuration options which controls how the data migration is run. Check the [source code](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/background_data_migrations/migration_helpers.rb) for the list of all available configuration options.
93
149
 
94
- ## Custom Background Migration Arguments
150
+ ## Custom Data Migration Arguments
95
151
 
96
- Background migrations may need additional information, supplied via arguments, to run.
152
+ Data migrations may need additional information to run, which can be provided via arguments.
97
153
 
98
154
  Declare that the migration class is accepting additional arguments:
99
155
 
100
156
  ```ruby
101
- class MyMigrationWithArgs < OnlineMigrations::BackgroundMigration
157
+ class MyMigrationWithArgs < OnlineMigrations::DataMigration
102
158
  def initialize(arg1, arg2, ...)
103
159
  @arg1 = arg1
104
160
  @arg2 = arg2
105
- ...
161
+ # ...
106
162
  end
107
163
  # ...
108
164
  end
@@ -124,12 +180,12 @@ def down
124
180
  end
125
181
  ```
126
182
 
127
- ## Considerations when writing Background Migrations
183
+ ## Considerations when writing Data Migrations
128
184
 
129
- * **Isolation**: Background migrations should be isolated and not use application code (for example, models defined in `app/models`). Since these migrations can take a long time to run it's possible for new versions to be deployed while they are still running.
130
- * **Idempotence**: It should be safe to run `process_batch` multiple times for the same elements. It's important if the Background Migration errors and you run it again, because the same element that errored may be processed again. Make sure that in case that your migration job is going to be retried data integrity is guaranteed.
185
+ * **Isolation**: Data migrations should be isolated and not use application code (for example, models defined in `app/models`). Since these migrations can take a long time to run it's possible for new versions to be deployed while they are still running.
186
+ * **Idempotence**: It should be safe to run `process` multiple times for the same elements. It's important, because if the data migration errored and you run it again, the same element that errored may be processed again. Make sure that if your migration is going to be retried the data integrity is guaranteed.
131
187
 
132
- ## Predefined background migrations
188
+ ## Predefined data migrations
133
189
 
134
190
  * `BackfillColumn` - backfills column(s) with scalar values (enqueue using `backfill_column_in_background`; or `backfill_column_for_type_change_in_background` if backfilling column for which type change is in progress)
135
191
  * `CopyColumn` - copies data from one column(s) to other(s) (enqueue using `copy_column_in_background`)
@@ -138,7 +194,7 @@ end
138
194
  * `PerformActionOnRelation` - performs specific action on a relation or individual records (enqueue using `perform_action_on_relation_in_background`)
139
195
  * `ResetCounters` - resets one or more counter caches to their correct value (enqueue using `reset_counters_in_background`)
140
196
 
141
- **Note**: These migration helpers should be run inside the migration against the database where background migrations tables are defined.
197
+ **Note**: These migration helpers should be run inside the migration files against the database where background migrations tables are defined.
142
198
 
143
199
  ## Depending on migrated data
144
200
 
@@ -146,19 +202,19 @@ You shouldn't depend on the data until the background data migration is finished
146
202
 
147
203
  ## Testing
148
204
 
149
- At a minimum, it's recommended that the `#process_batch` method in your background migration is tested. You may also want to test the `#relation` and `#count` methods if they are sufficiently complex.
205
+ At a minimum, it's recommended that the `#process` method in your data migration is tested. You may also want to test the `#collection` and `#count` methods if they are sufficiently complex.
150
206
 
151
207
  Example:
152
208
 
153
209
  ```ruby
154
- # test/online_migrations/background_migrations/backfill_project_issues_count_test.rb
210
+ # test/online_migrations/data_migrations/backfill_project_issues_count_test.rb
155
211
 
156
212
  require "test_helper"
157
213
 
158
214
  module OnlineMigrations
159
- module BackgroundMigrations
215
+ module DataMigrations
160
216
  class BackfillProjectIssuesCountTest < ActiveSupport::TestCase
161
- test "#process_batch performs an iteration" do
217
+ test "#process backfills issues_count" do
162
218
  rails = Project.create!(name: "Ruby on Rails")
163
219
  postgres = Project.create!(name: "PostgreSQL")
164
220
 
@@ -166,7 +222,9 @@ module OnlineMigrations
166
222
  postgres.issues.create!
167
223
 
168
224
  migration = BackfillProjectIssuesCount.new
169
- migration.process_batch(migration.relation)
225
+ migration.collection.each do |relation|
226
+ migration.process(relation)
227
+ end
170
228
 
171
229
  assert_equal 2, rails.reload.issues_count
172
230
  assert_equal 1, postgres.reload.issues_count
@@ -178,27 +236,27 @@ end
178
236
 
179
237
  ## Instrumentation
180
238
 
181
- Background migrations use the [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) API.
239
+ Data migrations use the [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) API.
182
240
 
183
- You can subscribe to `background_migrations` events and log it, graph it, etc.
241
+ You can subscribe to `background_data_migrations` events and log it, graph it, etc.
184
242
 
185
- To get notified about specific type of events, subscribe to the event name followed by the `background_migrations` namespace. E.g. for retries use:
243
+ To get notified about specific type of events, subscribe to the event name followed by the `background_data_migrations` namespace.
186
244
 
187
245
  ```ruby
188
246
  # config/initializers/online_migrations.rb
189
- ActiveSupport::Notifications.subscribe("retried.background_migrations") do |name, start, finish, id, payload|
190
- # background migration job object is available in payload[:background_migration_job]
247
+ ActiveSupport::Notifications.subscribe("started.background_data_migrations") do |name, start, finish, id, payload|
248
+ # background data migration object is available in payload[:migration]
191
249
 
192
250
  # Your code here
193
251
  end
194
252
  ```
195
253
 
196
- If you want to subscribe to every `background_migrations` event, use:
254
+ If you want to subscribe to every `background_data_migrations` event, use:
197
255
 
198
256
  ```ruby
199
257
  # config/initializers/online_migrations.rb
200
- ActiveSupport::Notifications.subscribe(/background_migrations/) do |name, start, finish, id, payload|
201
- # background migration job object is available in payload[:background_migration_job]
258
+ ActiveSupport::Notifications.subscribe(/background_data_migrations/) do |name, start, finish, id, payload|
259
+ # background data migration object is available in payload[:migration]
202
260
 
203
261
  # Your code here
204
262
  end
@@ -206,43 +264,70 @@ end
206
264
 
207
265
  Available events:
208
266
 
209
- * `started.background_migrations`
210
- * `process_batch.background_migrations`
211
- * `completed.background_migrations`
212
- * `retried.background_migrations`
213
- * `throttled.background_migrations`
267
+ * `started.background_data_migrations`
268
+ * `completed.background_data_migrations`
269
+ * `throttled.background_data_migrations`
270
+
271
+ ### Using Data Migration Callbacks
272
+
273
+ The data migrations provides callbacks that hook into its life cycle.
214
274
 
215
- ## Monitoring Background Migrations
275
+ Available callbacks are:
216
276
 
217
- Background Migrations can be in various states during its execution:
277
+ * `after_start`
278
+ * `around_process`
279
+ * `after_resume`
280
+ * `after_stop`
281
+ * `after_complete`
282
+ * `after_pause`
283
+ * `after_cancel`
284
+
285
+ ```ruby
286
+ module OnlineMigrations
287
+ module DataMigrations
288
+ class BackfillProjectIssuesCount < OnlineMigrations::DataMigration
289
+ def after_start
290
+ NotifyJob.perform_later(self.class.name)
291
+ end
292
+
293
+ # ...
294
+ end
295
+ end
296
+ end
297
+ ```
298
+
299
+ ## Monitoring Data Migrations
300
+
301
+ Data Migrations can be in various states during its execution:
218
302
 
219
303
  * **enqueued**: A migration has been enqueued by the user.
220
304
  * **running**: A migration is being performed by a migration executor.
305
+ * **pausing**: A migration has been told to pause but is finishing work.
221
306
  * **paused**: A migration was paused in the middle of the run by the user.
222
307
 
223
308
  To manually pause a migration, you can run:
224
309
 
225
310
  ```ruby
226
- migration = OnlineMigrations::BackgroundMigrations::Migration.find(id)
227
- migration.paused!
311
+ migration = OnlineMigrations::DataMigrations::Migration.find(id)
312
+ migration.pause
228
313
  ```
229
- * **finishing**: A migration is being manually finishing inline by the user.
230
- For example, if you need to manually perform a background migration until it is finished, you can run:
231
314
 
232
- ```ruby
233
- migration = OnlineMigrations::BackgroundMigrations::Migration.find(id)
234
- runner = OnlineMigrations::BackgroundMigrations::MigrationRunner.new(migration)
235
- runner.finish
236
- ```
237
- Note: In normal circumstances, this should not be used since background migrations should be run and finished by the scheduler.
238
315
  * **failed**: A migration raises an exception when running.
239
316
  * **succeeded**: A migration finished without error.
317
+ * **cancelling**: A migration has been told to cancel but is finishing work.
240
318
  * **cancelled**: A migration was cancelled by the user.
241
319
 
242
- To get the progress (assuming `#count` method on background migration class was defined):
320
+ To manually cancel a migration, you can run:
321
+
322
+ ```ruby
323
+ migration = OnlineMigrations::DataMigrations::Migration.find(id)
324
+ migration.cancel
325
+ ```
326
+
327
+ To get the progress (assuming `#count` method on data migration class was defined):
243
328
 
244
329
  ```ruby
245
- migration = OnlineMigrations::BackgroundMigrations::Migration.find(id)
330
+ migration = OnlineMigrations::DataMigrations::Migration.find(id)
246
331
  migration.progress # value from 0 to 100.0
247
332
  ```
248
333
 
@@ -253,51 +338,30 @@ migration.progress # value from 0 to 100.0
253
338
  To retry a failed migration, run:
254
339
 
255
340
  ```ruby
256
- migration = OnlineMigrations::BackgroundMigrations::Migration.find(id)
341
+ migration = OnlineMigrations::DataMigrations::Migration.find(id)
257
342
  migration.retry # => `true` if scheduled to be retried, `false` - if not
258
343
  ```
259
344
 
260
345
  The migration will be retried on the next Scheduler run.
261
346
 
262
- ## Cancelling a migration
263
-
264
- To cancel an existing migration from future performing, run:
265
-
266
- ```ruby
267
- migration = OnlineMigrations::BackgroundMigrations::Migration.find(id)
268
- migration.cancel
269
- ```
270
-
271
347
  ## Configuring
272
348
 
273
- There are a few configurable options for the Background Migrations. Custom configurations should be placed in a `online_migrations.rb` initializer.
349
+ There are a few configurable options for the data migrations. Custom configurations should be placed in a `online_migrations.rb` initializer.
274
350
 
275
- Check the [source code](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/background_migrations/config.rb) for the list of all available configuration options.
276
-
277
- **Note**: You can dynamically change certain migration parameters while the migration is run.
278
- For example,
279
- ```ruby
280
- migration = OnlineMigrations::BackgroundMigrations::Migration.find(id)
281
- migration.update!(
282
- batch_size: 50_000, # The # of records migration will update per run
283
- sub_batch_size: 10_000, # The # of records migration will update via single `UPDATE`
284
- batch_pause: 1.second, # Minimum time (in seconds) between successive migration runs
285
- sub_batch_pause_ms: 20 # Minimum time (in ms) between successive migration `UPDATE`s
286
- )
287
- ```
351
+ Check the [source code](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/background_data_migrations/config.rb) for the list of all available configuration options.
288
352
 
289
353
  ### Customizing the error handler
290
354
 
291
- Exceptions raised while a Background Migration is performing are rescued and information about the error is persisted in the database.
355
+ Exceptions raised while a data migration is performing are rescued and information about the error is persisted in the database.
292
356
 
293
357
  If you want to integrate with an exception monitoring service (e.g. Bugsnag), you can define an error handler:
294
358
 
295
359
  ```ruby
296
360
  # config/initializers/online_migrations.rb
297
361
 
298
- config.background_migrations.error_handler = ->(error, errored_job) do
362
+ config.background_data_migrations.error_handler = ->(error, errored_migration) do
299
363
  Bugsnag.notify(error) do |notification|
300
- notification.add_metadata(:background_migration, { name: errored_job.migration_name })
364
+ notification.add_metadata(:background_data_migration, { name: errored_migration.name })
301
365
  end
302
366
  end
303
367
  ```
@@ -305,32 +369,52 @@ end
305
369
  The error handler should be a lambda that accepts 2 arguments:
306
370
 
307
371
  * `error`: The exception that was raised.
308
- * `errored_job`: An `OnlineMigrations::BackgroundMigrations::MigrationJob` object that represents a failed batch.
372
+ * `errored_migration`: An `OnlineMigrations::BackgroundDataMigrations::Migration` object that represents a migration.
309
373
 
310
- ### Customizing the background migrations path
374
+ ### Customizing the data migrations path
311
375
 
312
- `OnlineMigrations.config.background_migrations.migrations_path` can be configured to define where generated background migrations will be placed.
376
+ `OnlineMigrations.config.background_data_migrations.migrations_path` can be configured to define where generated data migrations will be placed.
313
377
 
314
378
  ```ruby
315
379
  # config/initializers/online_migrations.rb
316
380
 
317
- config.background_migrations.migrations_path = "app/lib"
381
+ config.background_data_migrations.migrations_path = "app/lib"
318
382
  ```
319
383
 
320
384
  If no value is specified, it will default to `"lib"`.
321
385
 
322
- ### Customizing the background migrations module
386
+ ### Customizing the data migrations module
387
+
388
+ `config.background_data_migrations.migrations_module` can be configured to define the module in which
389
+ data migrations will be placed.
390
+
391
+ ```ruby
392
+ # config/initializers/online_migrations.rb
393
+
394
+ config.background_data_migrations.migrations_module = "DataMigrationsModule"
395
+ ```
396
+
397
+ If no value is specified, it will default to `"OnlineMigrations::DataMigrations"`.
398
+
399
+ ### Customizing the underlying sidekiq job class
323
400
 
324
- `config.background_migrations.migrations_module` can be configured to define the module in which
325
- background migrations will be placed.
401
+ A custom sidekiq job class can be configured to define a job class for your data migrations to use.
326
402
 
327
403
  ```ruby
328
404
  # config/initializers/online_migrations.rb
329
405
 
330
- config.background_migrations.migrations_module = "BackgroundMigrationsModule"
406
+ config.background_data_migrations.job = "CustomMigrationJob"
407
+ ```
408
+
409
+ ```ruby
410
+ # app/jobs/custom_migration_job.rb
411
+
412
+ class CustomMigrationJob < OnlineMigrations::DataMigrations::MigrationJob
413
+ sidekiq_options queue: "low"
414
+ end
331
415
  ```
332
416
 
333
- If no value is specified, it will default to `"OnlineMigrations::BackgroundMigrations"`.
417
+ The job class **must inherit** from `OnlineMigrations::DataMigrations::MigrationJob`.
334
418
 
335
419
  ### Multiple databases and sharding
336
420
 
@@ -350,7 +434,9 @@ OnlineMigrations::ApplicationRecord.connects_to database: { writing: :shard_one
350
434
  By default, ActiveRecord uses the database config named `:primary` (if exists) under the environment section from the `database.yml`.
351
435
  Otherwise, the first config under the environment section is used.
352
436
 
353
- By default, the scheduler works on a single shard on each run. To run a separate scheduler per shard:
437
+ #### Parallelize processing by shards
438
+
439
+ By default, only a single data migration at a time is processed. To process a single data migration at a time *per shard*:
354
440
 
355
441
  ```ruby
356
442
  [:shard_one, :shard_two, :shard_three].each do |shard|
@@ -359,3 +445,16 @@ By default, the scheduler works on a single shard on each run. To run a separate
359
445
  end
360
446
  end
361
447
  ```
448
+
449
+ #### Change processing concurrency
450
+
451
+ By default, only a *single* data migration at a time is processed. To change the concurrency:
452
+
453
+ ```ruby
454
+ every 1.minute do
455
+ # Run 2 data migrations in parallel.
456
+ runner "OnlineMigrations.run_background_data_migrations(concurrency: 2)"
457
+ end
458
+ ```
459
+
460
+ **Note**: This configuration works perfectly well in combination with the `:shard` configuration from the previous section.
@@ -103,7 +103,7 @@ To get notified about specific type of events, subscribe to the event name follo
103
103
  ```ruby
104
104
  # config/initializers/online_migrations.rb
105
105
  ActiveSupport::Notifications.subscribe("retried.background_schema_migrations") do |name, start, finish, id, payload|
106
- # background schema migration object is available in payload[:background_schema_migration]
106
+ # background schema migration object is available in payload[:migration]
107
107
 
108
108
  # Your code here
109
109
  end
@@ -114,7 +114,7 @@ If you want to subscribe to every `background_schema_migrations` event, use:
114
114
  ```ruby
115
115
  # config/initializers/online_migrations.rb
116
116
  ActiveSupport::Notifications.subscribe(/background_schema_migrations/) do |name, start, finish, id, payload|
117
- # background schema migration object is available in payload[:background_schema_migration]
117
+ # background schema migration object is available in payload[:migration]
118
118
 
119
119
  # Your code here
120
120
  end
@@ -5,11 +5,11 @@ require "rails/generators/active_record/migration"
5
5
 
6
6
  module OnlineMigrations
7
7
  # @private
8
- class BackgroundMigrationGenerator < Rails::Generators::NamedBase
8
+ class DataMigrationGenerator < Rails::Generators::NamedBase
9
9
  include ActiveRecord::Generators::Migration
10
10
 
11
11
  source_root File.expand_path("templates", __dir__)
12
- desc "This generator creates a background migration related files."
12
+ desc "This generator creates a background data migration related files."
13
13
 
14
14
  def create_background_data_migration_file
15
15
  migrations_module_file_path = migrations_module.underscore
@@ -20,7 +20,7 @@ module OnlineMigrations
20
20
  class_path,
21
21
  "#{file_name}.rb"
22
22
  )
23
- template("background_data_migration.rb", template_file)
23
+ template("data_migration.rb", template_file)
24
24
  end
25
25
 
26
26
  def create_migration_file
@@ -33,7 +33,7 @@ module OnlineMigrations
33
33
  end
34
34
 
35
35
  def config
36
- OnlineMigrations.config.background_migrations
36
+ OnlineMigrations.config.background_data_migrations
37
37
  end
38
38
 
39
39
  def migration_parent
@@ -0,0 +1,34 @@
1
+ class ChangeBackgroundDataMigrations < <%= migration_parent %>
2
+ def change
3
+ safety_assured do
4
+ change_table :background_migrations do |t|
5
+ t.string :cursor
6
+ t.string :jid
7
+ t.bigint :tick_total
8
+ t.bigint :tick_count, default: 0, null: false
9
+ t.float :time_running, default: 0.0, null: false
10
+ t.string :error_class
11
+ t.string :error_message
12
+ t.string :backtrace, array: true
13
+ t.string :connection_class_name
14
+ end
15
+
16
+ rename_column :background_migrations, :batch_max_attempts, :max_attempts
17
+
18
+ [
19
+ "batch_column_name",
20
+ "min_value",
21
+ "max_value",
22
+ "batch_size",
23
+ "sub_batch_size",
24
+ "batch_pause",
25
+ "sub_batch_pause_ms",
26
+ "composite",
27
+ ].each do |column|
28
+ change_column_null :background_migrations, column, true
29
+ end
30
+
31
+ rename_table :background_migrations, :background_data_migrations
32
+ end
33
+ end
34
+ end