online_migrations 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/test.yml +112 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +113 -0
- data/.yardopts +1 -0
- data/BACKGROUND_MIGRATIONS.md +288 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +27 -0
- data/Gemfile.lock +108 -0
- data/LICENSE.txt +21 -0
- data/README.md +1067 -0
- data/Rakefile +23 -0
- data/gemfiles/activerecord_42.gemfile +6 -0
- data/gemfiles/activerecord_50.gemfile +5 -0
- data/gemfiles/activerecord_51.gemfile +5 -0
- data/gemfiles/activerecord_52.gemfile +5 -0
- data/gemfiles/activerecord_60.gemfile +5 -0
- data/gemfiles/activerecord_61.gemfile +5 -0
- data/gemfiles/activerecord_70.gemfile +5 -0
- data/gemfiles/activerecord_head.gemfile +5 -0
- data/lib/generators/online_migrations/background_migration_generator.rb +29 -0
- data/lib/generators/online_migrations/install_generator.rb +34 -0
- data/lib/generators/online_migrations/templates/background_migration.rb.tt +22 -0
- data/lib/generators/online_migrations/templates/initializer.rb.tt +94 -0
- data/lib/generators/online_migrations/templates/migration.rb.tt +46 -0
- data/lib/online_migrations/background_migration.rb +64 -0
- data/lib/online_migrations/background_migrations/advisory_lock.rb +62 -0
- data/lib/online_migrations/background_migrations/backfill_column.rb +52 -0
- data/lib/online_migrations/background_migrations/background_migration_class_validator.rb +36 -0
- data/lib/online_migrations/background_migrations/config.rb +98 -0
- data/lib/online_migrations/background_migrations/copy_column.rb +90 -0
- data/lib/online_migrations/background_migrations/migration.rb +210 -0
- data/lib/online_migrations/background_migrations/migration_helpers.rb +238 -0
- data/lib/online_migrations/background_migrations/migration_job.rb +92 -0
- data/lib/online_migrations/background_migrations/migration_job_runner.rb +63 -0
- data/lib/online_migrations/background_migrations/migration_job_status_validator.rb +27 -0
- data/lib/online_migrations/background_migrations/migration_runner.rb +97 -0
- data/lib/online_migrations/background_migrations/migration_status_validator.rb +45 -0
- data/lib/online_migrations/background_migrations/scheduler.rb +49 -0
- data/lib/online_migrations/batch_iterator.rb +87 -0
- data/lib/online_migrations/change_column_type_helpers.rb +587 -0
- data/lib/online_migrations/command_checker.rb +590 -0
- data/lib/online_migrations/command_recorder.rb +137 -0
- data/lib/online_migrations/config.rb +198 -0
- data/lib/online_migrations/copy_trigger.rb +91 -0
- data/lib/online_migrations/database_tasks.rb +19 -0
- data/lib/online_migrations/error_messages.rb +388 -0
- data/lib/online_migrations/foreign_key_definition.rb +17 -0
- data/lib/online_migrations/foreign_keys_collector.rb +33 -0
- data/lib/online_migrations/indexes_collector.rb +48 -0
- data/lib/online_migrations/lock_retrier.rb +250 -0
- data/lib/online_migrations/migration.rb +63 -0
- data/lib/online_migrations/migrator.rb +23 -0
- data/lib/online_migrations/schema_cache.rb +96 -0
- data/lib/online_migrations/schema_statements.rb +1042 -0
- data/lib/online_migrations/utils.rb +140 -0
- data/lib/online_migrations/version.rb +5 -0
- data/lib/online_migrations.rb +74 -0
- data/online_migrations.gemspec +28 -0
- metadata +119 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 10e5140728aecd56d8f79b52eda79f3244c50212a731db898f3fe36673082bdd
|
4
|
+
data.tar.gz: fa086fff375514e8779ef1d143af68037013373f1954ee4af2596ac3090f1ce9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c18e7f727443671759894c0320c050fbb789ce4015e49eeea751c07f9ec7232e4746c62554493e2118a08b2cc97a519c3645882d3a20c1091ed853a607039d54
|
7
|
+
data.tar.gz: e44ccdb25d5ea1da727e4c6828a55f7426322262388a7a1eb5f022bbd976116550dfbcd04efda289bc7340a5662343d0a283cb4f6fdd756aa7d07313e125dba5
|
@@ -0,0 +1,112 @@
|
|
1
|
+
name: Test
|
2
|
+
on: [push, pull_request]
|
3
|
+
|
4
|
+
jobs:
|
5
|
+
# Run the linter first for rapid feedback if some trivial stylistic issues
|
6
|
+
# slipped through the cracks.
|
7
|
+
lint:
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
steps:
|
10
|
+
- uses: actions/checkout@v2
|
11
|
+
- uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: 3.1.0
|
14
|
+
bundler-cache: true
|
15
|
+
- run: bundle exec rubocop
|
16
|
+
|
17
|
+
test:
|
18
|
+
needs: lint
|
19
|
+
runs-on: ubuntu-latest
|
20
|
+
services:
|
21
|
+
postgres:
|
22
|
+
image: postgres
|
23
|
+
env:
|
24
|
+
POSTGRES_DB: online_migrations_test
|
25
|
+
POSTGRES_PASSWORD: postgres
|
26
|
+
options: >-
|
27
|
+
--health-cmd pg_isready
|
28
|
+
--health-interval 10s
|
29
|
+
--health-timeout 5s
|
30
|
+
--health-retries 5
|
31
|
+
ports:
|
32
|
+
- 5432:5432
|
33
|
+
strategy:
|
34
|
+
matrix:
|
35
|
+
include:
|
36
|
+
- ruby-version: 2.4.10
|
37
|
+
gemfile: gemfiles/activerecord_42.gemfile
|
38
|
+
- ruby-version: 2.5.9
|
39
|
+
gemfile: gemfiles/activerecord_42.gemfile
|
40
|
+
- ruby-version: 2.6.9
|
41
|
+
gemfile: gemfiles/activerecord_42.gemfile
|
42
|
+
|
43
|
+
- ruby-version: 2.4.10
|
44
|
+
gemfile: gemfiles/activerecord_50.gemfile
|
45
|
+
- ruby-version: 2.5.9
|
46
|
+
gemfile: gemfiles/activerecord_50.gemfile
|
47
|
+
- ruby-version: 2.6.9
|
48
|
+
gemfile: gemfiles/activerecord_50.gemfile
|
49
|
+
- ruby-version: 2.7.5
|
50
|
+
gemfile: gemfiles/activerecord_50.gemfile
|
51
|
+
|
52
|
+
- ruby-version: 2.4.10
|
53
|
+
gemfile: gemfiles/activerecord_51.gemfile
|
54
|
+
- ruby-version: 2.5.9
|
55
|
+
gemfile: gemfiles/activerecord_51.gemfile
|
56
|
+
- ruby-version: 2.6.9
|
57
|
+
gemfile: gemfiles/activerecord_51.gemfile
|
58
|
+
- ruby-version: 2.7.5
|
59
|
+
gemfile: gemfiles/activerecord_51.gemfile
|
60
|
+
|
61
|
+
- ruby-version: 2.4.10
|
62
|
+
gemfile: gemfiles/activerecord_52.gemfile
|
63
|
+
- ruby-version: 2.5.9
|
64
|
+
gemfile: gemfiles/activerecord_52.gemfile
|
65
|
+
- ruby-version: 2.6.9
|
66
|
+
gemfile: gemfiles/activerecord_52.gemfile
|
67
|
+
- ruby-version: 2.7.5
|
68
|
+
gemfile: gemfiles/activerecord_52.gemfile
|
69
|
+
|
70
|
+
- ruby-version: 2.5.9
|
71
|
+
gemfile: gemfiles/activerecord_60.gemfile
|
72
|
+
- ruby-version: 2.6.9
|
73
|
+
gemfile: gemfiles/activerecord_60.gemfile
|
74
|
+
- ruby-version: 2.7.5
|
75
|
+
gemfile: gemfiles/activerecord_60.gemfile
|
76
|
+
- ruby-version: 3.0.3
|
77
|
+
gemfile: gemfiles/activerecord_60.gemfile
|
78
|
+
|
79
|
+
- ruby-version: 2.5.9
|
80
|
+
gemfile: gemfiles/activerecord_61.gemfile
|
81
|
+
- ruby-version: 2.6.9
|
82
|
+
gemfile: gemfiles/activerecord_61.gemfile
|
83
|
+
- ruby-version: 2.7.4
|
84
|
+
gemfile: gemfiles/activerecord_61.gemfile
|
85
|
+
- ruby-version: 3.0.3
|
86
|
+
gemfile: gemfiles/activerecord_61.gemfile
|
87
|
+
- ruby-version: 3.1.0
|
88
|
+
gemfile: gemfiles/activerecord_61.gemfile
|
89
|
+
|
90
|
+
- ruby-version: 2.7.4
|
91
|
+
gemfile: gemfiles/activerecord_70.gemfile
|
92
|
+
- ruby-version: 3.0.3
|
93
|
+
gemfile: gemfiles/activerecord_70.gemfile
|
94
|
+
- ruby-version: 3.1.0
|
95
|
+
gemfile: gemfiles/activerecord_70.gemfile
|
96
|
+
|
97
|
+
- ruby-version: 2.7.4
|
98
|
+
gemfile: gemfiles/activerecord_head.gemfile
|
99
|
+
- ruby-version: 3.0.3
|
100
|
+
gemfile: gemfiles/activerecord_head.gemfile
|
101
|
+
- ruby-version: 3.1.0
|
102
|
+
gemfile: gemfiles/activerecord_head.gemfile
|
103
|
+
env:
|
104
|
+
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
|
105
|
+
steps:
|
106
|
+
- uses: actions/checkout@v2
|
107
|
+
- uses: ruby/setup-ruby@v1
|
108
|
+
with:
|
109
|
+
ruby-version: ${{ matrix.ruby-version }}
|
110
|
+
bundler-cache: true
|
111
|
+
- name: Run the test suite
|
112
|
+
run: bundle exec rake test
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
AllCops:
|
2
|
+
NewCops: disable
|
3
|
+
SuggestExtensions: false
|
4
|
+
|
5
|
+
Style/StringLiterals:
|
6
|
+
EnforcedStyle: double_quotes
|
7
|
+
|
8
|
+
Style/RescueModifier:
|
9
|
+
Exclude:
|
10
|
+
- test/**/*
|
11
|
+
|
12
|
+
Style/IfUnlessModifier:
|
13
|
+
Enabled: false
|
14
|
+
|
15
|
+
Style/SymbolArray:
|
16
|
+
EnforcedStyle: brackets
|
17
|
+
|
18
|
+
Style/WordArray:
|
19
|
+
EnforcedStyle: brackets
|
20
|
+
|
21
|
+
Style/GuardClause:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
Style/Documentation:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Style/MutableConstant:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Style/MissingRespondToMissing:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
Style/TrailingCommaInHashLiteral:
|
34
|
+
EnforcedStyleForMultiline: comma
|
35
|
+
|
36
|
+
Style/TrailingCommaInArrayLiteral:
|
37
|
+
EnforcedStyleForMultiline: comma
|
38
|
+
|
39
|
+
Style/NumericPredicate:
|
40
|
+
Enabled: false
|
41
|
+
|
42
|
+
Style/NegatedIf:
|
43
|
+
Enabled: false
|
44
|
+
|
45
|
+
Style/ConditionalAssignment:
|
46
|
+
Enabled: false
|
47
|
+
|
48
|
+
# only for ruby 2.3+
|
49
|
+
Style/SafeNavigation:
|
50
|
+
Enabled: false
|
51
|
+
|
52
|
+
Style/NumericLiterals:
|
53
|
+
Enabled: false
|
54
|
+
|
55
|
+
Style/Next:
|
56
|
+
Enabled: false
|
57
|
+
|
58
|
+
Style/GlobalVars:
|
59
|
+
Exclude:
|
60
|
+
- test/**/*
|
61
|
+
|
62
|
+
Style/Lambda:
|
63
|
+
EnforcedStyle: literal
|
64
|
+
|
65
|
+
Style/WhileUntilModifier:
|
66
|
+
Enabled: false
|
67
|
+
|
68
|
+
Style/HashAsLastArrayItem:
|
69
|
+
Enabled: false
|
70
|
+
|
71
|
+
# we can not use new syntax for older rubies
|
72
|
+
Lint/ErbNewArguments:
|
73
|
+
Enabled: false
|
74
|
+
|
75
|
+
Lint/MissingSuper:
|
76
|
+
Enabled: false
|
77
|
+
|
78
|
+
Layout/EmptyLinesAroundAccessModifier:
|
79
|
+
EnforcedStyle: only_before
|
80
|
+
|
81
|
+
Layout/IndentationConsistency:
|
82
|
+
EnforcedStyle: indented_internal_methods
|
83
|
+
|
84
|
+
Layout/ArgumentAlignment:
|
85
|
+
Enabled: false
|
86
|
+
|
87
|
+
Layout/LineLength:
|
88
|
+
Enabled: false
|
89
|
+
|
90
|
+
Layout/MultilineMethodCallIndentation:
|
91
|
+
Enabled: false
|
92
|
+
|
93
|
+
Layout/HashAlignment:
|
94
|
+
Enabled: false
|
95
|
+
|
96
|
+
Naming/VariableNumber:
|
97
|
+
Exclude:
|
98
|
+
- test/**/*
|
99
|
+
|
100
|
+
Naming/MethodParameterName:
|
101
|
+
AllowedNames:
|
102
|
+
- of
|
103
|
+
- fk
|
104
|
+
|
105
|
+
Naming/AccessorMethodName:
|
106
|
+
Exclude:
|
107
|
+
- test/**/*
|
108
|
+
|
109
|
+
Gemspec/RequiredRubyVersion:
|
110
|
+
Enabled: false
|
111
|
+
|
112
|
+
Metrics:
|
113
|
+
Enabled: false
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private --markup markdown lib/**/**.rb - README.md
|
@@ -0,0 +1,288 @@
|
|
1
|
+
# Background Migrations
|
2
|
+
|
3
|
+
When a project grows, your database starts to be heavy and changing the data through the deployment process can be very painful.
|
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.
|
6
|
+
|
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
|
+
|
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::BackgroundMigrations::Scheduler.run"
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
## Creating a Background Migration
|
26
|
+
|
27
|
+
A generator is provided to create background migrations. Generate a new background migration by running:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
$ bin/rails generate online_migrations:background_migration backfill_project_issues_count
|
31
|
+
```
|
32
|
+
|
33
|
+
This creates the background migration file `lib/online_migrations/background_migrations/backfill_project_issues_count.rb`.
|
34
|
+
|
35
|
+
The generated class is a subclass of `OnlineMigrations::BackgroundMigration` that implements:
|
36
|
+
|
37
|
+
* `relation`: return an `ActiveRecord::Relation` to be iterated over
|
38
|
+
* `process_batch`: do the work of your background migration on a batch (`ActiveRecord::Relation`)
|
39
|
+
* `count`: return the number of rows that will be iterated over (optional, to be
|
40
|
+
able to show progress)
|
41
|
+
|
42
|
+
Example:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
# lib/online_migrations/background_migrations/backfill_project_issues_count.rb
|
46
|
+
|
47
|
+
module OnlineMigrations
|
48
|
+
module BackgroundMigrations
|
49
|
+
class BackfillProjectIssuesCount < OnlineMigrations::BackgroundMigration
|
50
|
+
class Project < ActiveRecord::Base; end
|
51
|
+
|
52
|
+
def relation
|
53
|
+
Project.all
|
54
|
+
end
|
55
|
+
|
56
|
+
def process_batch(projects)
|
57
|
+
projects.update_all(<<~SQL)
|
58
|
+
issues_count = (
|
59
|
+
SELECT COUNT(*)
|
60
|
+
FROM issues
|
61
|
+
WHERE issues.project_id = projects.id
|
62
|
+
)
|
63
|
+
SQL
|
64
|
+
end
|
65
|
+
|
66
|
+
def count
|
67
|
+
relation.count
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
## Enqueueing a Background Migration
|
75
|
+
|
76
|
+
You can enqueue your background migration to be run by the scheduler via:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
# db/migrate/xxxxxxxxxxxxxx_backfill_project_issues_count.rb
|
80
|
+
# ...
|
81
|
+
def up
|
82
|
+
enqueue_background_migration("BackfillProjectIssuesCount")
|
83
|
+
end
|
84
|
+
# ...
|
85
|
+
```
|
86
|
+
|
87
|
+
`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/online_migrations.rb) for the list of all available configuration options.
|
88
|
+
|
89
|
+
## Custom Background Migration Arguments
|
90
|
+
|
91
|
+
Background migrations may need additional information, supplied via arguments, to run.
|
92
|
+
|
93
|
+
Declare that the migration class is accepting additional arguments:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
class MyMigrationWithArgs < OnlineMigrations::BackgroundMigration
|
97
|
+
def initialize(arg1, arg2, ...)
|
98
|
+
@arg1 = arg1
|
99
|
+
@arg2 = arg2
|
100
|
+
...
|
101
|
+
end
|
102
|
+
# ...
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
And pass them when enqueuing:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
enqueue_background_migration("MyMigrationWithArgs", arg1, arg2, ...)
|
110
|
+
```
|
111
|
+
|
112
|
+
## Considerations when writing Background Migrations
|
113
|
+
|
114
|
+
* **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.
|
115
|
+
* **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.
|
116
|
+
|
117
|
+
## Testing
|
118
|
+
|
119
|
+
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.
|
120
|
+
|
121
|
+
Example:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
# test/online_migrations/background_migrations/backfill_project_issues_count_test.rb
|
125
|
+
|
126
|
+
require "test_helper"
|
127
|
+
|
128
|
+
module OnlineMigrations
|
129
|
+
module BackgroundMigrations
|
130
|
+
class BackfillProjectIssuesCountTest < ActiveSupport::TestCase
|
131
|
+
test "#process_batch performs a background migration iteration" do
|
132
|
+
rails = Project.create!(name: "rails")
|
133
|
+
postgres = Project.create!(name: "PostgreSQL")
|
134
|
+
|
135
|
+
2.times { rails.issues.create! }
|
136
|
+
_postgres_issue = postgres.issues.create!
|
137
|
+
|
138
|
+
BackfillProjectIssuesCount.new.process_batch(Project.all)
|
139
|
+
|
140
|
+
assert_equal 2, rails.issues_count
|
141
|
+
assert_equal 1, postgres.issues_count
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
## Instrumentation
|
149
|
+
|
150
|
+
Background migrations use the [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) API.
|
151
|
+
|
152
|
+
You can subscribe to `background_migrations` events and log it, graph it, etc.
|
153
|
+
|
154
|
+
To get notified about specific type of events, subscribe to the event name followed by the `background_migrations` namespace. E.g. for retries use:
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
# config/initializers/online_migrations.rb
|
158
|
+
ActiveSupport::Notifications.subscribe("retried.background_migrations") do |name, start, finish, id, payload|
|
159
|
+
# background migration job object is available in payload[:background_migration_job]
|
160
|
+
|
161
|
+
# Your code here
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
If you want to subscribe to every `background_migrations` event, use:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
# config/initializers/online_migrations.rb
|
169
|
+
ActiveSupport::Notifications.subscribe(/background_migrations/) do |name, start, finish, id, payload|
|
170
|
+
# background migration job object is available in payload[:background_migration_job]
|
171
|
+
|
172
|
+
# Your code here
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
Available events:
|
177
|
+
|
178
|
+
* `started.background_migrations`
|
179
|
+
* `process_batch.background_migrations`
|
180
|
+
* `completed.background_migrations`
|
181
|
+
* `retried.background_migrations`
|
182
|
+
* `throttled.background_migrations`
|
183
|
+
|
184
|
+
## Monitoring Background Migrations
|
185
|
+
|
186
|
+
Background Migrations can be in various states during its execution:
|
187
|
+
|
188
|
+
* **enqueued**: A migration has been enqueued by the user.
|
189
|
+
* **running**: A migration is being performed by a migration executor.
|
190
|
+
* **paused**: A migration was paused in the middle of the run by the user.
|
191
|
+
|
192
|
+
To manually pause a migration, you can run:
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
migration = OnlineMigrations::BackgroundMigrations::Migration.find(id)
|
196
|
+
migration.paused!
|
197
|
+
```
|
198
|
+
* **finishing**: A migration is being manually finishing inline by the user.
|
199
|
+
For example, if you need to manually perform a background migration until it is finished, you can run:
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
migration = OnlineMigrations::BackgroundMigrations::Migration.find(id)
|
203
|
+
runner = OnlineMigrations::BackgroundMigrations::MigrationRunner.new(migration)
|
204
|
+
runner.finish
|
205
|
+
```
|
206
|
+
Note: In normal circumstances, this should not be used since background migrations should be run and finished by the scheduler.
|
207
|
+
* **failed**: A migration raises an exception when running.
|
208
|
+
* **succeeded**: A migration finished without error.
|
209
|
+
|
210
|
+
To get the progress (assuming `#count` method on background migration class was defined):
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
migration = OnlineMigrations::BackgroundMigrations::Migration.find(id)
|
214
|
+
migration.progress # value from 0 to 1.0
|
215
|
+
```
|
216
|
+
|
217
|
+
**Note**: It will be easier to work with background migrations through some kind of Web UI, but until it is implemented, we can work with them only manually.
|
218
|
+
|
219
|
+
## Configuring
|
220
|
+
|
221
|
+
There are a few configurable options for the Background Migrations. Custom configurations should be placed in a `online_migrations.rb` initializer.
|
222
|
+
|
223
|
+
**Note**: 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.
|
224
|
+
|
225
|
+
### Throttling
|
226
|
+
|
227
|
+
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.
|
228
|
+
|
229
|
+
Specify the throttle condition as a block:
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
# config/initializers/online_migrations.rb
|
233
|
+
|
234
|
+
OnlineMigrations.config.backround_migrations.throttler = -> { DatabaseStatus.unhealthy? }
|
235
|
+
```
|
236
|
+
|
237
|
+
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.
|
238
|
+
|
239
|
+
### Customizing the error handler
|
240
|
+
|
241
|
+
Exceptions raised while a Background Migration is performing are rescued and information about the error is persisted in the database.
|
242
|
+
|
243
|
+
If you want to integrate with an exception monitoring service (e.g. Bugsnag), you can define an error handler:
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
# config/initializers/online_migrations.rb
|
247
|
+
|
248
|
+
OnlineMigrations.config.backround_migrations.error_handler = ->(error, errored_job) do
|
249
|
+
Bugsnag.notify(error) do |notification|
|
250
|
+
notification.add_metadata(:background_migration, { name: errored_job.migration_name })
|
251
|
+
end
|
252
|
+
end
|
253
|
+
```
|
254
|
+
|
255
|
+
The error handler should be a lambda that accepts 2 arguments:
|
256
|
+
|
257
|
+
* `error`: The exception that was raised.
|
258
|
+
* `errored_job`: An `OnlineMigrations::BackgroundMigrations::MigrationJob` object that represents a failed batch.
|
259
|
+
* `errored_element`: The `OnlineMigrations::BackgroundMigrations::MigrationJob` object representing a batch,
|
260
|
+
that was being processed when the Background Migration raised an exception.
|
261
|
+
|
262
|
+
### Customizing the background migrations module
|
263
|
+
|
264
|
+
`OnlineMigrations.config.background_migrations.migrations_module` can be configured to define the module in which
|
265
|
+
background migrations will be placed.
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
# config/initializers/online_migrations.rb
|
269
|
+
|
270
|
+
OnlineMigrations.config.background_migrations.migrations_module = "BackgroundMigrationsModule"
|
271
|
+
```
|
272
|
+
|
273
|
+
If no value is specified, it will default to `OnlineMigrations::BackgroundMigrations`.
|
274
|
+
|
275
|
+
### Customizing the backtrace cleaner
|
276
|
+
|
277
|
+
`OnlineMigrations.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.
|
278
|
+
|
279
|
+
```ruby
|
280
|
+
# config/initializers/online_migrations.rb
|
281
|
+
|
282
|
+
cleaner = ActiveSupport::BacktraceCleaner.new
|
283
|
+
cleaner.add_silencer { |line| line =~ /ignore_this_dir/ }
|
284
|
+
|
285
|
+
OnlineMigrations.config.background_migrations.backtrace_cleaner = cleaner
|
286
|
+
```
|
287
|
+
|
288
|
+
If none is specified, the default `Rails.backtrace_cleaner` will be used to clean backtraces.
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in online_migrations.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem "minitest", "~> 5.0"
|
9
|
+
gem "rake", "~> 12.0"
|
10
|
+
gem "yard"
|
11
|
+
|
12
|
+
if defined?(@ar_gem_requirement)
|
13
|
+
gem "activerecord", @ar_gem_requirement
|
14
|
+
gem "railties", @ar_gem_requirement
|
15
|
+
else
|
16
|
+
gem "activerecord" # latest
|
17
|
+
gem "railties" # to test generator
|
18
|
+
|
19
|
+
# Run Rubocop only on latest rubies, because it is incompatible with older versions.
|
20
|
+
gem "rubocop", "~> 1.24"
|
21
|
+
end
|
22
|
+
|
23
|
+
if defined?(@pg_gem_requirement)
|
24
|
+
gem "pg", @pg_gem_requirement
|
25
|
+
else
|
26
|
+
gem "pg", "~> 1.2"
|
27
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
online_migrations (0.1.0)
|
5
|
+
activerecord (>= 4.2)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
actionpack (7.0.1)
|
11
|
+
actionview (= 7.0.1)
|
12
|
+
activesupport (= 7.0.1)
|
13
|
+
rack (~> 2.0, >= 2.2.0)
|
14
|
+
rack-test (>= 0.6.3)
|
15
|
+
rails-dom-testing (~> 2.0)
|
16
|
+
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
17
|
+
actionview (7.0.1)
|
18
|
+
activesupport (= 7.0.1)
|
19
|
+
builder (~> 3.1)
|
20
|
+
erubi (~> 1.4)
|
21
|
+
rails-dom-testing (~> 2.0)
|
22
|
+
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
23
|
+
activemodel (7.0.1)
|
24
|
+
activesupport (= 7.0.1)
|
25
|
+
activerecord (7.0.1)
|
26
|
+
activemodel (= 7.0.1)
|
27
|
+
activesupport (= 7.0.1)
|
28
|
+
activesupport (7.0.1)
|
29
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
30
|
+
i18n (>= 1.6, < 2)
|
31
|
+
minitest (>= 5.1)
|
32
|
+
tzinfo (~> 2.0)
|
33
|
+
ast (2.4.2)
|
34
|
+
builder (3.2.4)
|
35
|
+
concurrent-ruby (1.1.9)
|
36
|
+
crass (1.0.6)
|
37
|
+
erubi (1.10.0)
|
38
|
+
i18n (1.8.11)
|
39
|
+
concurrent-ruby (~> 1.0)
|
40
|
+
loofah (2.13.0)
|
41
|
+
crass (~> 1.0.2)
|
42
|
+
nokogiri (>= 1.5.9)
|
43
|
+
method_source (1.0.0)
|
44
|
+
mini_portile2 (2.7.1)
|
45
|
+
minitest (5.15.0)
|
46
|
+
nokogiri (1.13.0)
|
47
|
+
mini_portile2 (~> 2.7.0)
|
48
|
+
racc (~> 1.4)
|
49
|
+
parallel (1.21.0)
|
50
|
+
parser (3.1.0.0)
|
51
|
+
ast (~> 2.4.1)
|
52
|
+
pg (1.2.3)
|
53
|
+
racc (1.6.0)
|
54
|
+
rack (2.2.3)
|
55
|
+
rack-test (1.1.0)
|
56
|
+
rack (>= 1.0, < 3)
|
57
|
+
rails-dom-testing (2.0.3)
|
58
|
+
activesupport (>= 4.2.0)
|
59
|
+
nokogiri (>= 1.6)
|
60
|
+
rails-html-sanitizer (1.4.2)
|
61
|
+
loofah (~> 2.3)
|
62
|
+
railties (7.0.1)
|
63
|
+
actionpack (= 7.0.1)
|
64
|
+
activesupport (= 7.0.1)
|
65
|
+
method_source
|
66
|
+
rake (>= 12.2)
|
67
|
+
thor (~> 1.0)
|
68
|
+
zeitwerk (~> 2.5)
|
69
|
+
rainbow (3.0.0)
|
70
|
+
rake (12.3.3)
|
71
|
+
regexp_parser (2.2.0)
|
72
|
+
rexml (3.2.5)
|
73
|
+
rubocop (1.24.1)
|
74
|
+
parallel (~> 1.10)
|
75
|
+
parser (>= 3.0.0.0)
|
76
|
+
rainbow (>= 2.2.2, < 4.0)
|
77
|
+
regexp_parser (>= 1.8, < 3.0)
|
78
|
+
rexml
|
79
|
+
rubocop-ast (>= 1.15.1, < 2.0)
|
80
|
+
ruby-progressbar (~> 1.7)
|
81
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
82
|
+
rubocop-ast (1.15.1)
|
83
|
+
parser (>= 3.0.1.1)
|
84
|
+
ruby-progressbar (1.11.0)
|
85
|
+
thor (1.2.1)
|
86
|
+
tzinfo (2.0.4)
|
87
|
+
concurrent-ruby (~> 1.0)
|
88
|
+
unicode-display_width (2.1.0)
|
89
|
+
webrick (1.7.0)
|
90
|
+
yard (0.9.27)
|
91
|
+
webrick (~> 1.7.0)
|
92
|
+
zeitwerk (2.5.3)
|
93
|
+
|
94
|
+
PLATFORMS
|
95
|
+
ruby
|
96
|
+
|
97
|
+
DEPENDENCIES
|
98
|
+
activerecord
|
99
|
+
minitest (~> 5.0)
|
100
|
+
online_migrations!
|
101
|
+
pg (~> 1.2)
|
102
|
+
railties
|
103
|
+
rake (~> 12.0)
|
104
|
+
rubocop (~> 1.24)
|
105
|
+
yard
|
106
|
+
|
107
|
+
BUNDLED WITH
|
108
|
+
2.1.4
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 fatkodima
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|