online_migrations 0.14.1 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +13 -9
- data/docs/{background_migrations.md → background_data_migrations.md} +26 -40
- data/docs/background_schema_migrations.md +163 -0
- data/docs/configuring.md +35 -4
- data/lib/generators/online_migrations/background_migration_generator.rb +12 -1
- data/lib/generators/online_migrations/install_generator.rb +1 -1
- data/lib/generators/online_migrations/templates/create_background_schema_migrations.rb.tt +29 -0
- data/lib/generators/online_migrations/templates/initializer.rb.tt +24 -11
- data/lib/generators/online_migrations/templates/install_migration.rb.tt +75 -0
- data/lib/generators/online_migrations/templates/migration.rb.tt +7 -48
- data/lib/generators/online_migrations/upgrade_generator.rb +5 -0
- data/lib/online_migrations/application_record.rb +11 -0
- data/lib/online_migrations/background_migrations/background_migration_class_validator.rb +0 -7
- data/lib/online_migrations/background_migrations/config.rb +27 -24
- data/lib/online_migrations/background_migrations/delete_associated_records.rb +1 -1
- data/lib/online_migrations/background_migrations/migration.rb +1 -8
- data/lib/online_migrations/background_migrations/migration_helpers.rb +16 -2
- data/lib/online_migrations/background_migrations/migration_job.rb +5 -2
- data/lib/online_migrations/background_migrations/migration_job_runner.rb +2 -1
- data/lib/online_migrations/background_migrations/migration_runner.rb +1 -1
- data/lib/online_migrations/background_schema_migrations/config.rb +40 -0
- data/lib/online_migrations/background_schema_migrations/migration.rb +205 -0
- data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +76 -0
- data/lib/online_migrations/background_schema_migrations/migration_runner.rb +110 -0
- data/lib/online_migrations/background_schema_migrations/migration_status_validator.rb +33 -0
- data/lib/online_migrations/background_schema_migrations/scheduler.rb +30 -0
- data/lib/online_migrations/command_checker.rb +31 -1
- data/lib/online_migrations/command_recorder.rb +5 -0
- data/lib/online_migrations/config.rb +32 -0
- data/lib/online_migrations/error_messages.rb +3 -3
- data/lib/online_migrations/lock_retrier.rb +0 -2
- data/lib/online_migrations/schema_statements.rb +1 -0
- data/lib/online_migrations/utils.rb +12 -0
- data/lib/online_migrations/version.rb +1 -1
- data/lib/online_migrations.rb +19 -2
- metadata +13 -4
- data/lib/online_migrations/background_migrations/application_record.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48ddbce66257c9010d8ab7361d8860cd898854207b5a1f4c6aa2b6b797aec2e1
|
4
|
+
data.tar.gz: c21fbb5f535f788e13068e339eebe77f05a378c68c00244c4522a10d50a28a0d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5bbff2a8cca45e5fce79d653db3246bc41a4ab9d314f44131bbf74f2659d610fdaf3e87ac5e22baf7928d0cdb481e19eacaa1ed8945e4ee32f1afac3223273f
|
7
|
+
data.tar.gz: 6e3861f420327e0bccec7ab8e7c0a508e7cf224312c366297834091467d7cdb5e4fac8a305dbb59f7c0650d199d9a17062ab0408fd180b6fbaa05946bfd27545
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
## master (unreleased)
|
2
2
|
|
3
|
+
## 0.16.0 (2024-03-28)
|
4
|
+
|
5
|
+
- Add support for asynchronous creation/removal of indexes
|
6
|
+
|
7
|
+
See `docs/background_schema_migrations.md` for the feature description.
|
8
|
+
|
9
|
+
## 0.15.0 (2024-03-19)
|
10
|
+
|
11
|
+
- Reraise errors when running background migrations inline
|
12
|
+
- Add `remove_background_migration` migration helper
|
13
|
+
- Allow adding bigint foreign keys referencing integer primary keys
|
14
|
+
- Fix `add_reference_concurrently` to check for mismatched key types
|
15
|
+
|
3
16
|
## 0.14.1 (2024-02-21)
|
4
17
|
|
5
18
|
- Fix `MigrationRunner` to consider `run_background_migrations_inline` proc
|
data/README.md
CHANGED
@@ -40,11 +40,11 @@ $ bin/rails generate online_migrations:install
|
|
40
40
|
$ bin/rails db:migrate
|
41
41
|
```
|
42
42
|
|
43
|
-
**Note**: If you do not have plans on using [background migrations](docs/
|
43
|
+
**Note**: If you do not have plans on using [background data migrations](docs/background_data_migrations.md) or [background schema migrations](docs/background_schema_migrations.md) features, then you can delete the generated migration and regenerate it later, if needed.
|
44
44
|
|
45
45
|
### Upgrading
|
46
46
|
|
47
|
-
If you're already using [background migrations](docs/
|
47
|
+
If you're already using [background data migrations](docs/background_data_migrations.md) or [background schema migrations](docs/background_schema_migrations.md), your background migrations tables may require additional columns. After every upgrade run:
|
48
48
|
|
49
49
|
```sh
|
50
50
|
$ bin/rails generate online_migrations:upgrade
|
@@ -191,7 +191,7 @@ end
|
|
191
191
|
|
192
192
|
```ruby
|
193
193
|
class User < ApplicationRecord
|
194
|
-
self.ignored_columns
|
194
|
+
self.ignored_columns += ["name"]
|
195
195
|
end
|
196
196
|
```
|
197
197
|
|
@@ -285,7 +285,7 @@ end
|
|
285
285
|
```
|
286
286
|
|
287
287
|
**Note**: If you forget `disable_ddl_transaction!`, the migration will fail.
|
288
|
-
**Note**: You may consider [background migrations](#background-migrations) to run data changes on large tables.
|
288
|
+
**Note**: You may consider [background data migrations](#background-data-migrations) or [background schema migrations](#background-schema-migrations) to run data changes on large tables.
|
289
289
|
|
290
290
|
### Changing the type of a column
|
291
291
|
|
@@ -488,7 +488,7 @@ It will use a combination of a VIEW and column aliasing to work with both column
|
|
488
488
|
|
489
489
|
```ruby
|
490
490
|
class User < ApplicationRecord
|
491
|
-
self.ignored_columns
|
491
|
+
self.ignored_columns += ["name"]
|
492
492
|
end
|
493
493
|
```
|
494
494
|
|
@@ -1172,7 +1172,7 @@ A safer approach is to:
|
|
1172
1172
|
|
1173
1173
|
```ruby
|
1174
1174
|
class User < ApplicationRecord
|
1175
|
-
self.ignored_columns
|
1175
|
+
self.ignored_columns += ["type"]
|
1176
1176
|
end
|
1177
1177
|
```
|
1178
1178
|
|
@@ -1228,9 +1228,13 @@ Certain methods like `execute` and `change_table` cannot be inspected and are pr
|
|
1228
1228
|
|
1229
1229
|
Read [configuring.md](docs/configuring.md).
|
1230
1230
|
|
1231
|
-
## Background Migrations
|
1231
|
+
## Background Data Migrations
|
1232
1232
|
|
1233
|
-
Read [
|
1233
|
+
Read [background_data_migrations.md](docs/background_data_migrations.md) on how to perform data migrations on large tables.
|
1234
|
+
|
1235
|
+
## Background Schema Migrations
|
1236
|
+
|
1237
|
+
Read [background_schema_migrations.md](docs/background_schema_migrations.md) on how to perform background schema migrations on large tables.
|
1234
1238
|
|
1235
1239
|
## Credits
|
1236
1240
|
|
@@ -1295,7 +1299,7 @@ The main differences are:
|
|
1295
1299
|
* adding different types of constraints
|
1296
1300
|
* and others
|
1297
1301
|
|
1298
|
-
2. This gem has a
|
1302
|
+
2. This gem has a powerful internal framework for running [data migrations](docs/background_data_migrations.md) and [schema migrations](docs/background_schema_migrations.md) on very large tables in background.
|
1299
1303
|
|
1300
1304
|
For example, you can use background migrations to migrate data that’s stored in a single JSON column to a separate table instead; backfill values from one column to another (as one of the steps when changing column type); or backfill some column’s value from an API.
|
1301
1305
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Background Migrations
|
1
|
+
# Background Data Migrations
|
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
|
|
@@ -18,7 +18,7 @@ Start a background migrations scheduler. For example, to run it on cron using [w
|
|
18
18
|
|
19
19
|
```ruby
|
20
20
|
every 1.minute do
|
21
|
-
runner "OnlineMigrations.
|
21
|
+
runner "OnlineMigrations.run_background_data_migrations"
|
22
22
|
end
|
23
23
|
```
|
24
24
|
|
@@ -30,7 +30,8 @@ A generator is provided to create background migrations. Generate a new backgrou
|
|
30
30
|
$ bin/rails generate online_migrations:background_migration backfill_project_issues_count
|
31
31
|
```
|
32
32
|
|
33
|
-
This creates the background migration file `lib/online_migrations/background_migrations/backfill_project_issues_count.rb
|
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.
|
34
35
|
|
35
36
|
The generated class is a subclass of `OnlineMigrations::BackgroundMigration` that implements:
|
36
37
|
|
@@ -76,12 +77,16 @@ end
|
|
76
77
|
You can enqueue your background migration to be run by the scheduler via:
|
77
78
|
|
78
79
|
```ruby
|
79
|
-
# db/migrate/
|
80
|
-
|
81
|
-
def up
|
82
|
-
|
80
|
+
# db/migrate/xxxxxxxxxxxxxx_enqueue_backfill_project_issues_count.rb
|
81
|
+
class EnqueueBackfillProjectIssuesCount < ActiveRecord::Migration[7.1]
|
82
|
+
def up
|
83
|
+
enqueue_background_migration("BackfillProjectIssuesCount")
|
84
|
+
end
|
85
|
+
|
86
|
+
def down
|
87
|
+
remove_background_migration("BackfillProjectIssuesCount")
|
88
|
+
end
|
83
89
|
end
|
84
|
-
# ...
|
85
90
|
```
|
86
91
|
|
87
92
|
`enqueue_background_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.
|
@@ -106,7 +111,17 @@ end
|
|
106
111
|
And pass them when enqueuing:
|
107
112
|
|
108
113
|
```ruby
|
109
|
-
|
114
|
+
def up
|
115
|
+
enqueue_background_migration("MyMigrationWithArgs", arg1, arg2, ...)
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
Make sure to also pass the arguments inside the `down` method of the migration:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
def down
|
123
|
+
remove_background_migration("MyMigrationWithArgs", arg1, arg2, ...)
|
124
|
+
end
|
110
125
|
```
|
111
126
|
|
112
127
|
## Considerations when writing Background Migrations
|
@@ -246,20 +261,6 @@ migration.update!(
|
|
246
261
|
)
|
247
262
|
```
|
248
263
|
|
249
|
-
### Throttling
|
250
|
-
|
251
|
-
Background Migrations often modify a lot of data and can be taxing on your database. There is a throttling mechanism that can be used to throttle a background migration when a given condition is met. If a migration is throttled, it will be interrupted and retried on the next Scheduler cycle run.
|
252
|
-
|
253
|
-
Specify the throttle condition as a block:
|
254
|
-
|
255
|
-
```ruby
|
256
|
-
# config/initializers/online_migrations.rb
|
257
|
-
|
258
|
-
config.background_migrations.throttler = -> { DatabaseStatus.unhealthy? }
|
259
|
-
```
|
260
|
-
|
261
|
-
Note that it's up to you to define a throttling condition that makes sense for your app. For example, you can check various PostgreSQL metrics such as replication lag, DB threads, whether DB writes are available, etc.
|
262
|
-
|
263
264
|
### Customizing the error handler
|
264
265
|
|
265
266
|
Exceptions raised while a Background Migration is performing are rescued and information about the error is persisted in the database.
|
@@ -306,21 +307,6 @@ config.background_migrations.migrations_module = "BackgroundMigrationsModule"
|
|
306
307
|
|
307
308
|
If no value is specified, it will default to `"OnlineMigrations::BackgroundMigrations"`.
|
308
309
|
|
309
|
-
### Customizing the backtrace cleaner
|
310
|
-
|
311
|
-
`config.background_migrations.backtrace_cleaner` can be configured to specify a backtrace cleaner to use when a Background Migration errors and the backtrace is cleaned and persisted. An `ActiveSupport::BacktraceCleaner` should be used.
|
312
|
-
|
313
|
-
```ruby
|
314
|
-
# config/initializers/online_migrations.rb
|
315
|
-
|
316
|
-
cleaner = ActiveSupport::BacktraceCleaner.new
|
317
|
-
cleaner.add_silencer { |line| line =~ /ignore_this_dir/ }
|
318
|
-
|
319
|
-
config.background_migrations.backtrace_cleaner = cleaner
|
320
|
-
```
|
321
|
-
|
322
|
-
If none is specified, the default `Rails.backtrace_cleaner` will be used to clean backtraces.
|
323
|
-
|
324
310
|
### Multiple databases and sharding
|
325
311
|
|
326
312
|
If you have multiple databases or sharding, you may need to configure where background migrations related tables live
|
@@ -330,10 +316,10 @@ by configuring the parent model:
|
|
330
316
|
# config/initializers/online_migrations.rb
|
331
317
|
|
332
318
|
# Referring to one of the databases
|
333
|
-
OnlineMigrations::
|
319
|
+
OnlineMigrations::ApplicationRecord.connects_to database: { writing: :animals }
|
334
320
|
|
335
321
|
# Referring to one of the shards (via `:database` option)
|
336
|
-
OnlineMigrations::
|
322
|
+
OnlineMigrations::ApplicationRecord.connects_to database: { writing: :shard_one }
|
337
323
|
```
|
338
324
|
|
339
325
|
By default, ActiveRecord uses the database config named `:primary` (if exists) under the environment section from the `database.yml`.
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# Background Schema Migrations
|
2
|
+
|
3
|
+
When a project grows, your database starts to be heavy and performing schema changes through the deployment process can be very painful.
|
4
|
+
|
5
|
+
E.g., for very large tables, index creation can be a challenge to manage. While adding indexes `CONCURRENTLY` creates indexes in a way that does not block ordinary traffic, it can still be problematic when index creation runs for many hours. Necessary database operations like autovacuum cannot run, and the deployment process is usually blocked waiting for index creation to finish.
|
6
|
+
|
7
|
+
**Note**: You probably don't need to use this feature for smaller projects, since performing schema changes directly on smaller databases will be perfectly fine and will not block the deployment too much.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Make sure you have migration files generated when installed this gem:
|
12
|
+
|
13
|
+
```sh
|
14
|
+
$ bin/rails generate online_migrations:install
|
15
|
+
```
|
16
|
+
|
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:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
every 1.minute do
|
21
|
+
runner "OnlineMigrations.run_background_schema_migrations"
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
or run it manually when the deployment is finished, from the rails console:
|
26
|
+
|
27
|
+
```rb
|
28
|
+
[production] (main)> OnlineMigrations.run_background_schema_migrations
|
29
|
+
```
|
30
|
+
|
31
|
+
**Note**: Scheduler will perform only one migration at a time, to not load the database too much. If you enqueued multiple migrations or a migration for multiple shards, you need to call this method a few times.
|
32
|
+
|
33
|
+
**Note**: Make sure that the process that runs the scheduler does not die until the migration is finished.
|
34
|
+
|
35
|
+
## Enqueueing a Background Schema Migration
|
36
|
+
|
37
|
+
Currently, only helpers for adding/removing indexes are provided.
|
38
|
+
|
39
|
+
Background schema migrations should be performed in 2 steps:
|
40
|
+
|
41
|
+
1. Create a PR that schedules the index to be created/removed
|
42
|
+
2. Verify that the PR was deployed and that the index was actually created/removed on production.
|
43
|
+
Create a follow-up PR with a regular migration that creates/removes an index synchronously (will be a no op when run on production) and commit the schema changes for `schema.rb`/`structure.sql`
|
44
|
+
|
45
|
+
To schedule an index creation:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
# db/migrate/xxxxxxxxxxxxxx_add_index_to_users_email_in_background.rb
|
49
|
+
def up
|
50
|
+
add_index_in_background(:users, :email, unique: true)
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
To schedule an index removal:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
# db/migrate/xxxxxxxxxxxxxx_remove_index_from_users_email_in_background.rb
|
58
|
+
def up
|
59
|
+
remove_index_in_background(:users, name: "index_users_on_email")
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
`add_index_in_background`/`remove_index_in_background` accept additional configuration options which controls how the background schema migration is run. Check the [source code](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/background_schema_migrations/migration_helpers.rb) for the list of all available configuration options.
|
64
|
+
|
65
|
+
## Instrumentation
|
66
|
+
|
67
|
+
Background schema migrations use the [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) API.
|
68
|
+
|
69
|
+
You can subscribe to `background_schema_migrations` events and log it, graph it, etc.
|
70
|
+
|
71
|
+
To get notified about specific type of events, subscribe to the event name followed by the `background_schema_migrations` namespace. E.g. for retries use:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# config/initializers/online_migrations.rb
|
75
|
+
ActiveSupport::Notifications.subscribe("retried.background_schema_migrations") do |name, start, finish, id, payload|
|
76
|
+
# background schema migration object is available in payload[:background_schema_migration]
|
77
|
+
|
78
|
+
# Your code here
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
If you want to subscribe to every `background_schema_migrations` event, use:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
# config/initializers/online_migrations.rb
|
86
|
+
ActiveSupport::Notifications.subscribe(/background_schema_migrations/) do |name, start, finish, id, payload|
|
87
|
+
# background schema migration object is available in payload[:background_schema_migration]
|
88
|
+
|
89
|
+
# Your code here
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
Available events:
|
94
|
+
|
95
|
+
* `started.background_schema_migrations`
|
96
|
+
* `run.background_schema_migrations`
|
97
|
+
* `completed.background_schema_migrations`
|
98
|
+
* `retried.background_schema_migrations`
|
99
|
+
* `throttled.background_schema_migrations`
|
100
|
+
|
101
|
+
## Monitoring Background Schema Migrations
|
102
|
+
|
103
|
+
Background Schema Migrations can be in various states during its execution:
|
104
|
+
|
105
|
+
* **enqueued**: A migration has been enqueued by the user.
|
106
|
+
* **running**: A migration is being performed by a migration executor.
|
107
|
+
* **failed**: A migration raises an exception when running.
|
108
|
+
* **succeeded**: A migration finished without error.
|
109
|
+
|
110
|
+
## Configuring
|
111
|
+
|
112
|
+
There are a few configurable options for the Background Schema Migrations. Custom configurations should be placed in a `online_migrations.rb` initializer.
|
113
|
+
|
114
|
+
Check the [source code](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/background_schema_migrations/config.rb) for the list of all available configuration options.
|
115
|
+
|
116
|
+
**Note**: You can dynamically change certain migration parameters while the migration is run.
|
117
|
+
For example,
|
118
|
+
```ruby
|
119
|
+
migration = OnlineMigrations::BackgroundSchemaMigrations::Migration.find(id)
|
120
|
+
migration.update!(
|
121
|
+
statement_timeout: 2.hours, # The statement timeout value used when running the migration
|
122
|
+
max_attempts: 10 # The # of attempts the failing migration will be retried
|
123
|
+
)
|
124
|
+
```
|
125
|
+
|
126
|
+
### Customizing the error handler
|
127
|
+
|
128
|
+
Exceptions raised while a Background Schema Migration is performing are rescued and information about the error is persisted in the database.
|
129
|
+
|
130
|
+
If you want to integrate with an exception monitoring service (e.g. Bugsnag), you can define an error handler:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
# config/initializers/online_migrations.rb
|
134
|
+
|
135
|
+
OnlineMigrations.config.background_schema_migrations.error_handler = ->(error, errored_migration) do
|
136
|
+
Bugsnag.notify(error) do |notification|
|
137
|
+
notification.add_metadata(:background_schema_migration, { name: errored_migration.name })
|
138
|
+
end
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
142
|
+
The error handler should be a lambda that accepts 2 arguments:
|
143
|
+
|
144
|
+
* `error`: The exception that was raised.
|
145
|
+
* `errored_migration`: An `OnlineMigrations::BackgroundSchemaMigrations::Migration` object that represents a failed migration.
|
146
|
+
|
147
|
+
### Multiple databases and sharding
|
148
|
+
|
149
|
+
If you have multiple databases or sharding, you may need to configure where background migrations related tables live
|
150
|
+
by configuring the parent model:
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
# config/initializers/online_migrations.rb
|
154
|
+
|
155
|
+
# Referring to one of the databases
|
156
|
+
OnlineMigrations::ApplicationRecord.connects_to database: { writing: :animals }
|
157
|
+
|
158
|
+
# Referring to one of the shards (via `:database` option)
|
159
|
+
OnlineMigrations::ApplicationRecord.connects_to database: { writing: :shard_one }
|
160
|
+
```
|
161
|
+
|
162
|
+
By default, ActiveRecord uses the database config named `:primary` (if exists) under the environment section from the `database.yml`.
|
163
|
+
Otherwise, the first config under the environment section is used.
|
data/docs/configuring.md
CHANGED
@@ -101,13 +101,15 @@ config.lock_retrier = OnlineMigrations::ExponentialLockRetrier.new(
|
|
101
101
|
)
|
102
102
|
```
|
103
103
|
|
104
|
-
When statement within transaction fails - the whole transaction is retried.
|
104
|
+
When a statement within transaction fails - the whole transaction is retried. If any statement fails when running outside a transaction (e.g. using `disable_ddl_transaction!`) then only that statement is retried.
|
105
105
|
|
106
|
-
|
106
|
+
**Note**: Statements are retried by default, unless lock retries are disabled. It is possible to implement more sophisticated lock retriers. See [source code](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/lock_retrier.rb) for the examples.
|
107
107
|
|
108
108
|
To temporarily disable lock retries while running migrations, set `DISABLE_LOCK_RETRIES` env variable. This is useful when you are deploying a hotfix and do not want to wait too long while the lock retrier safely tries to acquire the lock, but try to acquire the lock immediately with the default configured lock timeout value.
|
109
109
|
|
110
|
-
|
110
|
+
To permanently disable lock retries, you can set `lock_retrier` to `nil`.
|
111
|
+
|
112
|
+
Finally, if your lock retrier implementation does not have an explicit `lock_timeout` value configured, then the timeout behavior will fallback to the database configuration (`config/database.yml`) or the PostgreSQL server config value ([off by default](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-LOCK-TIMEOUT)). Take care configuring this value, as this fallback may result in your migrations running without a lock timeout!
|
111
113
|
|
112
114
|
## Existing Migrations
|
113
115
|
|
@@ -214,7 +216,7 @@ Add to an initializer file:
|
|
214
216
|
config.auto_analyze = true
|
215
217
|
```
|
216
218
|
|
217
|
-
|
219
|
+
## Running background migrations inline
|
218
220
|
|
219
221
|
`config.run_background_migrations_inline` can be configured with a proc to decide whether background migrations should be run inline. For convenience defaults to true for development and test environments.
|
220
222
|
|
@@ -225,6 +227,35 @@ config.run_background_migrations_inline = -> { Rails.env.local? }
|
|
225
227
|
|
226
228
|
Set to `nil` to avoid running background migrations inline.
|
227
229
|
|
230
|
+
## Throttling
|
231
|
+
|
232
|
+
Background data and schema migrations can be taxing on your database. There is a throttling mechanism that can be used to throttle a background migration when a given condition is met. If a migration is throttled, it will be interrupted and retried on the next Scheduler cycle run.
|
233
|
+
|
234
|
+
Specify the throttle condition as a block:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
# config/initializers/online_migrations.rb
|
238
|
+
|
239
|
+
OnlineMigrations.config.throttler = -> { DatabaseStatus.unhealthy? }
|
240
|
+
```
|
241
|
+
|
242
|
+
**Note**: It's up to you to define a throttling condition that makes sense for your app. For example, you can check various PostgreSQL metrics such as replication lag, DB threads, whether DB writes are available, etc.
|
243
|
+
|
244
|
+
## Customizing the backtrace cleaner
|
245
|
+
|
246
|
+
`OnlineMigrations.config.backtrace_cleaner` can be configured to specify a backtrace cleaner to use when a background data or schema migration errors and the backtrace is cleaned and persisted. An `ActiveSupport::BacktraceCleaner` should be used.
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
# config/initializers/online_migrations.rb
|
250
|
+
|
251
|
+
cleaner = ActiveSupport::BacktraceCleaner.new
|
252
|
+
cleaner.add_silencer { |line| line =~ /ignore_this_dir/ }
|
253
|
+
|
254
|
+
OnlineMigrations.config.backtrace_cleaner = cleaner
|
255
|
+
```
|
256
|
+
|
257
|
+
If none is specified, the default `Rails.backtrace_cleaner` will be used to clean backtraces.
|
258
|
+
|
228
259
|
## Schema Sanity
|
229
260
|
|
230
261
|
Columns can flip order in `db/schema.rb` when you have multiple developers. One way to prevent this is to [alphabetize them](https://www.pgrs.net/2008/03/12/alphabetize-schema-rb-columns/).
|
@@ -1,12 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails/generators"
|
4
|
+
require "rails/generators/active_record/migration"
|
4
5
|
|
5
6
|
module OnlineMigrations
|
6
7
|
# @private
|
7
8
|
class BackgroundMigrationGenerator < Rails::Generators::NamedBase
|
9
|
+
include ActiveRecord::Generators::Migration
|
10
|
+
|
8
11
|
source_root File.expand_path("templates", __dir__)
|
9
|
-
desc "This generator creates a background migration
|
12
|
+
desc "This generator creates a background migration related files."
|
10
13
|
|
11
14
|
def create_background_migration_file
|
12
15
|
migrations_module_file_path = migrations_module.underscore
|
@@ -20,6 +23,10 @@ module OnlineMigrations
|
|
20
23
|
template("background_migration.rb", template_file)
|
21
24
|
end
|
22
25
|
|
26
|
+
def create_migration_file
|
27
|
+
migration_template("migration.rb", File.join(db_migrate_path, "enqueue_#{file_name}.rb"))
|
28
|
+
end
|
29
|
+
|
23
30
|
private
|
24
31
|
def migrations_module
|
25
32
|
config.migrations_module
|
@@ -28,5 +35,9 @@ module OnlineMigrations
|
|
28
35
|
def config
|
29
36
|
OnlineMigrations.config.background_migrations
|
30
37
|
end
|
38
|
+
|
39
|
+
def migration_parent
|
40
|
+
"ActiveRecord::Migration[#{Utils.ar_version}]"
|
41
|
+
end
|
31
42
|
end
|
32
43
|
end
|
@@ -15,7 +15,7 @@ module OnlineMigrations
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def create_migration_file
|
18
|
-
migration_template("
|
18
|
+
migration_template("install_migration.rb", File.join(db_migrate_path, "install_online_migrations.rb"))
|
19
19
|
end
|
20
20
|
|
21
21
|
private
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class CreateBackgroundSchemaMigrations < <%= migration_parent %>
|
2
|
+
def change
|
3
|
+
# You can remove this migration for now and regenerate it later if you do not have plans
|
4
|
+
# to use background schema migrations, like adding indexes in the background.
|
5
|
+
create_table :background_schema_migrations do |t|
|
6
|
+
t.bigint :parent_id
|
7
|
+
t.string :migration_name, null: false
|
8
|
+
t.string :table_name, null: false
|
9
|
+
t.string :definition, null: false
|
10
|
+
t.string :status, default: "enqueued", null: false
|
11
|
+
t.string :shard
|
12
|
+
t.boolean :composite, default: false, null: false
|
13
|
+
t.integer :statement_timeout
|
14
|
+
t.datetime :started_at
|
15
|
+
t.datetime :finished_at
|
16
|
+
t.integer :max_attempts, null: false
|
17
|
+
t.integer :attempts, default: 0, null: false
|
18
|
+
t.string :error_class
|
19
|
+
t.string :error_message
|
20
|
+
t.string :backtrace, array: true
|
21
|
+
t.string :connection_class_name
|
22
|
+
t.timestamps
|
23
|
+
|
24
|
+
t.foreign_key :background_schema_migrations, column: :parent_id, on_delete: :cascade
|
25
|
+
|
26
|
+
t.index [:migration_name, :shard], unique: true, name: :index_background_schema_migrations_on_unique_configuration
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -77,12 +77,20 @@ OnlineMigrations.configure do |config|
|
|
77
77
|
# end
|
78
78
|
# end
|
79
79
|
|
80
|
-
# Decide whether background migrations should be run inline.
|
80
|
+
# Decide whether background data and schema migrations should be run inline.
|
81
81
|
# Convenient for development and test environments.
|
82
82
|
config.run_background_migrations_inline = -> { Rails.env.local? }
|
83
83
|
|
84
|
-
#
|
84
|
+
# Configure custom throttler for background data and schema migrations.
|
85
|
+
# It will be called before each run.
|
86
|
+
# If throttled, the current run will be retried next time.
|
87
|
+
# config.throttler = -> { DatabaseStatus.unhealthy? }
|
88
|
+
|
89
|
+
# The Active Support backtrace cleaner that will be used to clean the
|
90
|
+
# backtrace of a background data or schema migration that errors.
|
91
|
+
config.backtrace_cleaner = Rails.backtrace_cleaner
|
85
92
|
|
93
|
+
# ==> Background data migrations configuration
|
86
94
|
# The path where generated background migrations will be placed.
|
87
95
|
# config.background_migrations.migrations_path = "lib"
|
88
96
|
|
@@ -105,22 +113,27 @@ OnlineMigrations.configure do |config|
|
|
105
113
|
# When attempts are exhausted, the individual batch is marked as failed.
|
106
114
|
# config.background_migrations.batch_max_attempts = 5
|
107
115
|
|
108
|
-
# Configure custom throttler for background migrations.
|
109
|
-
# It will be called before each batch run.
|
110
|
-
# If throttled, the current run will be retried next time.
|
111
|
-
# config.background_migrations.throttler = -> { DatabaseStatus.unhealthy? }
|
112
|
-
|
113
116
|
# The number of seconds that must pass before the running job is considered stuck.
|
114
117
|
# config.background_migrations.stuck_jobs_timeout = 1.hour
|
115
118
|
|
116
|
-
# The Active Support backtrace cleaner that will be used to clean the
|
117
|
-
# backtrace of a migration job that errors.
|
118
|
-
config.background_migrations.backtrace_cleaner = Rails.backtrace_cleaner
|
119
|
-
|
120
119
|
# The callback to perform when an error occurs in the migration job.
|
121
120
|
# config.background_migrations.error_handler = ->(error, errored_job) do
|
122
121
|
# Bugsnag.notify(error) do |notification|
|
123
122
|
# notification.add_metadata(:background_migration, { name: errored_job.migration_name })
|
124
123
|
# end
|
125
124
|
# end
|
125
|
+
|
126
|
+
# ==> Background schema migrations configuration
|
127
|
+
# When attempts are exhausted, the failing migration stops to be retried.
|
128
|
+
# config.background_schema_migrations.max_attempts = 5
|
129
|
+
|
130
|
+
# Statement timeout value used when running background schema migration.
|
131
|
+
# config.background_schema_migrations.statement_timeout = 1.hour
|
132
|
+
|
133
|
+
# The callback to perform when an error occurs during the background schema migration.
|
134
|
+
# config.background_schema_migrations.error_handler = ->(error, errored_migration) do
|
135
|
+
# Bugsnag.notify(error) do |notification|
|
136
|
+
# notification.add_metadata(:background_schema_migration, { name: errored_migration.name })
|
137
|
+
# end
|
138
|
+
# end
|
126
139
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
class InstallOnlineMigrations < <%= migration_parent %>
|
2
|
+
def change
|
3
|
+
create_table :background_migrations do |t|
|
4
|
+
t.bigint :parent_id
|
5
|
+
t.string :migration_name, null: false
|
6
|
+
t.jsonb :arguments, default: [], null: false
|
7
|
+
t.string :batch_column_name, null: false
|
8
|
+
t.bigint :min_value, null: false
|
9
|
+
t.bigint :max_value, null: false
|
10
|
+
t.bigint :rows_count
|
11
|
+
t.integer :batch_size, null: false
|
12
|
+
t.integer :sub_batch_size, null: false
|
13
|
+
t.integer :batch_pause, null: false
|
14
|
+
t.integer :sub_batch_pause_ms, null: false
|
15
|
+
t.integer :batch_max_attempts, null: false
|
16
|
+
t.string :status, default: "enqueued", null: false
|
17
|
+
t.string :shard
|
18
|
+
t.boolean :composite, default: false, null: false
|
19
|
+
t.timestamps
|
20
|
+
|
21
|
+
t.foreign_key :background_migrations, column: :parent_id, on_delete: :cascade
|
22
|
+
|
23
|
+
t.index [:migration_name, :arguments, :shard],
|
24
|
+
unique: true, name: :index_background_migrations_on_unique_configuration
|
25
|
+
end
|
26
|
+
|
27
|
+
create_table :background_migration_jobs do |t|
|
28
|
+
t.bigint :migration_id, null: false
|
29
|
+
t.bigint :min_value, null: false
|
30
|
+
t.bigint :max_value, null: false
|
31
|
+
t.integer :batch_size, null: false
|
32
|
+
t.integer :sub_batch_size, null: false
|
33
|
+
t.integer :pause_ms, null: false
|
34
|
+
t.datetime :started_at
|
35
|
+
t.datetime :finished_at
|
36
|
+
t.string :status, default: "enqueued", null: false
|
37
|
+
t.integer :max_attempts, null: false
|
38
|
+
t.integer :attempts, default: 0, null: false
|
39
|
+
t.string :error_class
|
40
|
+
t.string :error_message
|
41
|
+
t.string :backtrace, array: true
|
42
|
+
t.timestamps
|
43
|
+
|
44
|
+
t.foreign_key :background_migrations, column: :migration_id, on_delete: :cascade
|
45
|
+
|
46
|
+
t.index [:migration_id, :max_value], name: :index_background_migration_jobs_on_max_value
|
47
|
+
t.index [:migration_id, :status, :updated_at], name: :index_background_migration_jobs_on_updated_at
|
48
|
+
t.index [:migration_id, :finished_at], name: :index_background_migration_jobs_on_finished_at
|
49
|
+
end
|
50
|
+
|
51
|
+
create_table :background_schema_migrations do |t|
|
52
|
+
t.bigint :parent_id
|
53
|
+
t.string :migration_name, null: false
|
54
|
+
t.string :table_name, null: false
|
55
|
+
t.string :definition, null: false
|
56
|
+
t.string :status, default: "enqueued", null: false
|
57
|
+
t.string :shard
|
58
|
+
t.boolean :composite, default: false, null: false
|
59
|
+
t.integer :statement_timeout
|
60
|
+
t.datetime :started_at
|
61
|
+
t.datetime :finished_at
|
62
|
+
t.integer :max_attempts, null: false
|
63
|
+
t.integer :attempts, default: 0, null: false
|
64
|
+
t.string :error_class
|
65
|
+
t.string :error_message
|
66
|
+
t.string :backtrace, array: true
|
67
|
+
t.string :connection_class_name
|
68
|
+
t.timestamps
|
69
|
+
|
70
|
+
t.foreign_key :background_schema_migrations, column: :parent_id, on_delete: :cascade
|
71
|
+
|
72
|
+
t.index [:migration_name, :shard], unique: true, name: :index_background_schema_migrations_on_unique_configuration
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|