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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -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 +160 -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 +17 -4
- data/lib/online_migrations/config.rb +4 -4
- data/lib/online_migrations/data_migration.rb +127 -0
- data/lib/online_migrations/error_messages.rb +2 -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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a3658cc979ead6b8dea71140f3ba95b0f7a4a4c26a27891f49a5b6925c94e59
|
4
|
+
data.tar.gz: 7d3ab5e09422e28078543ac67fa0ecae1a7afe9d8e29f67e55d6b772fb708e50
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
29
|
+
## Creating a Data Migration
|
26
30
|
|
27
|
-
A generator is provided to create
|
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:
|
34
|
+
$ bin/rails generate online_migrations:data_migration backfill_project_issues_count
|
31
35
|
```
|
32
36
|
|
33
|
-
This creates
|
34
|
-
and
|
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::
|
40
|
+
The generated class is a subclass of `OnlineMigrations::DataMigration` that implements:
|
37
41
|
|
38
|
-
* `
|
39
|
-
* `
|
40
|
-
* `count`: return
|
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/
|
49
|
+
# lib/online_migrations/data_migrations/backfill_project_issues_count.rb
|
47
50
|
|
48
51
|
module OnlineMigrations
|
49
|
-
module
|
50
|
-
class BackfillProjectIssuesCount < OnlineMigrations::
|
52
|
+
module DataMigrations
|
53
|
+
class BackfillProjectIssuesCount < OnlineMigrations::DataMigration
|
51
54
|
class Project < ActiveRecord::Base; end
|
52
55
|
|
53
|
-
def
|
54
|
-
Project.
|
56
|
+
def collection
|
57
|
+
Project.in_batches(of: 100)
|
55
58
|
end
|
56
59
|
|
57
|
-
def
|
58
|
-
|
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
|
-
|
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
|
-
|
108
|
+
### Customizing the Batch Size
|
76
109
|
|
77
|
-
|
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
|
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
|
150
|
+
## Custom Data Migration Arguments
|
95
151
|
|
96
|
-
|
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::
|
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
|
183
|
+
## Considerations when writing Data Migrations
|
128
184
|
|
129
|
-
* **Isolation**:
|
130
|
-
* **Idempotence**: It should be safe to run `
|
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
|
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 `#
|
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/
|
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
|
215
|
+
module DataMigrations
|
160
216
|
class BackfillProjectIssuesCountTest < ActiveSupport::TestCase
|
161
|
-
test "#
|
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.
|
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
|
-
|
239
|
+
Data migrations use the [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) API.
|
182
240
|
|
183
|
-
You can subscribe to `
|
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 `
|
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("
|
190
|
-
# background migration
|
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 `
|
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(/
|
201
|
-
# background migration
|
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.
|
210
|
-
* `
|
211
|
-
* `
|
212
|
-
|
213
|
-
|
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
|
-
|
275
|
+
Available callbacks are:
|
216
276
|
|
217
|
-
|
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::
|
227
|
-
migration.
|
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
|
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::
|
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::
|
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
|
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/
|
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
|
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.
|
362
|
+
config.background_data_migrations.error_handler = ->(error, errored_migration) do
|
299
363
|
Bugsnag.notify(error) do |notification|
|
300
|
-
notification.add_metadata(:
|
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
|
-
* `
|
372
|
+
* `errored_migration`: An `OnlineMigrations::BackgroundDataMigrations::Migration` object that represents a migration.
|
309
373
|
|
310
|
-
### Customizing the
|
374
|
+
### Customizing the data migrations path
|
311
375
|
|
312
|
-
`OnlineMigrations.config.
|
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.
|
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
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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[:
|
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[:
|
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
|
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("
|
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.
|
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
|