online_migrations 0.12.0 → 0.13.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 +17 -0
- data/docs/background_migrations.md +19 -7
- data/lib/generators/online_migrations/background_migration_generator.rb +8 -5
- data/lib/generators/online_migrations/templates/initializer.rb.tt +3 -0
- data/lib/online_migrations/background_migrations/config.rb +5 -0
- data/lib/online_migrations/background_migrations/migration.rb +9 -12
- data/lib/online_migrations/batch_iterator.rb +24 -15
- data/lib/online_migrations/change_column_type_helpers.rb +0 -7
- data/lib/online_migrations/schema_statements.rb +20 -13
- data/lib/online_migrations/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8952366397c54505f364a353dde1d68e988ac4773e9b3663de790a75c8b860f3
|
4
|
+
data.tar.gz: 376dc6e760a2b07d55968a11097f9f81067bc72291718938d1102fafb46a73ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e40ed5ab49e5d702c7d7cfebdb4264621375ad9affb67e8c3592814c4a832e75929c4ff718ce90709e64497226df3d9503a42babf56cfaad5ce973026f71d82
|
7
|
+
data.tar.gz: cdcbc9c26ac23fb975f21dd58c9826dab59c9571d074da06211dea2ffae72c3c57372722110ecd729ba649f21db551f58e8ca43689214f1235cd20669f033cf6
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
## master (unreleased)
|
2
2
|
|
3
|
+
## 0.13.1 (2024-01-23)
|
4
|
+
|
5
|
+
- Fix calculation of batch ranges for background migration created with explicit ranges
|
6
|
+
|
7
|
+
## 0.13.0 (2024-01-22)
|
8
|
+
|
9
|
+
- Add ability to configure the path where generated background migrations will be placed
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
# It is placed in lib/ by default.
|
13
|
+
config.background_migrations.migrations_path = "app/lib"
|
14
|
+
```
|
15
|
+
|
16
|
+
- Reduce number of queries needed to calculate batch ranges for background migrations
|
17
|
+
- Fix `finalize_column_type_change` to not recreate already existing indexes on the temporary column
|
18
|
+
- Remove potentially heavy queries used to get the ranges of a background migration
|
19
|
+
|
3
20
|
## 0.12.0 (2024-01-18)
|
4
21
|
|
5
22
|
- Require passing model name for background migration helpers when using multiple databases
|
@@ -255,7 +255,7 @@ Specify the throttle condition as a block:
|
|
255
255
|
```ruby
|
256
256
|
# config/initializers/online_migrations.rb
|
257
257
|
|
258
|
-
|
258
|
+
config.background_migrations.throttler = -> { DatabaseStatus.unhealthy? }
|
259
259
|
```
|
260
260
|
|
261
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.
|
@@ -269,7 +269,7 @@ If you want to integrate with an exception monitoring service (e.g. Bugsnag), yo
|
|
269
269
|
```ruby
|
270
270
|
# config/initializers/online_migrations.rb
|
271
271
|
|
272
|
-
|
272
|
+
config.background_migrations.error_handler = ->(error, errored_job) do
|
273
273
|
Bugsnag.notify(error) do |notification|
|
274
274
|
notification.add_metadata(:background_migration, { name: errored_job.migration_name })
|
275
275
|
end
|
@@ -281,22 +281,34 @@ The error handler should be a lambda that accepts 2 arguments:
|
|
281
281
|
* `error`: The exception that was raised.
|
282
282
|
* `errored_job`: An `OnlineMigrations::BackgroundMigrations::MigrationJob` object that represents a failed batch.
|
283
283
|
|
284
|
+
### Customizing the background migrations path
|
285
|
+
|
286
|
+
`OnlineMigrations.config.background_migrations.migrations_path` can be configured to define where generated background migrations will be placed.
|
287
|
+
|
288
|
+
```ruby
|
289
|
+
# config/initializers/online_migrations.rb
|
290
|
+
|
291
|
+
config.background_migrations.migrations_path = "app/lib"
|
292
|
+
```
|
293
|
+
|
294
|
+
If no value is specified, it will default to `"lib"`.
|
295
|
+
|
284
296
|
### Customizing the background migrations module
|
285
297
|
|
286
|
-
`
|
298
|
+
`config.background_migrations.migrations_module` can be configured to define the module in which
|
287
299
|
background migrations will be placed.
|
288
300
|
|
289
301
|
```ruby
|
290
302
|
# config/initializers/online_migrations.rb
|
291
303
|
|
292
|
-
|
304
|
+
config.background_migrations.migrations_module = "BackgroundMigrationsModule"
|
293
305
|
```
|
294
306
|
|
295
|
-
If no value is specified, it will default to `OnlineMigrations::BackgroundMigrations`.
|
307
|
+
If no value is specified, it will default to `"OnlineMigrations::BackgroundMigrations"`.
|
296
308
|
|
297
309
|
### Customizing the backtrace cleaner
|
298
310
|
|
299
|
-
`
|
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.
|
300
312
|
|
301
313
|
```ruby
|
302
314
|
# config/initializers/online_migrations.rb
|
@@ -304,7 +316,7 @@ If no value is specified, it will default to `OnlineMigrations::BackgroundMigrat
|
|
304
316
|
cleaner = ActiveSupport::BacktraceCleaner.new
|
305
317
|
cleaner.add_silencer { |line| line =~ /ignore_this_dir/ }
|
306
318
|
|
307
|
-
|
319
|
+
config.background_migrations.backtrace_cleaner = cleaner
|
308
320
|
```
|
309
321
|
|
310
322
|
If none is specified, the default `Rails.backtrace_cleaner` will be used to clean backtraces.
|
@@ -9,8 +9,11 @@ module OnlineMigrations
|
|
9
9
|
desc "This generator creates a background migration file."
|
10
10
|
|
11
11
|
def create_background_migration_file
|
12
|
+
migrations_module_file_path = migrations_module.underscore
|
13
|
+
|
12
14
|
template_file = File.join(
|
13
|
-
|
15
|
+
config.migrations_path,
|
16
|
+
migrations_module_file_path,
|
14
17
|
class_path,
|
15
18
|
"#{file_name}.rb"
|
16
19
|
)
|
@@ -18,12 +21,12 @@ module OnlineMigrations
|
|
18
21
|
end
|
19
22
|
|
20
23
|
private
|
21
|
-
def
|
22
|
-
migrations_module
|
24
|
+
def migrations_module
|
25
|
+
config.migrations_module
|
23
26
|
end
|
24
27
|
|
25
|
-
def
|
26
|
-
OnlineMigrations.config.background_migrations
|
28
|
+
def config
|
29
|
+
OnlineMigrations.config.background_migrations
|
27
30
|
end
|
28
31
|
end
|
29
32
|
end
|
@@ -79,6 +79,9 @@ OnlineMigrations.configure do |config|
|
|
79
79
|
|
80
80
|
# ==> Background migrations configuration
|
81
81
|
|
82
|
+
# The path where generated background migrations will be placed.
|
83
|
+
# config.background_migrations.migrations_path = "lib"
|
84
|
+
|
82
85
|
# The module in which background migrations will be placed.
|
83
86
|
# config.background_migrations.migrations_module = "OnlineMigrations::BackgroundMigrations"
|
84
87
|
|
@@ -4,6 +4,10 @@ module OnlineMigrations
|
|
4
4
|
module BackgroundMigrations
|
5
5
|
# Class representing configuration options for background migrations.
|
6
6
|
class Config
|
7
|
+
# The path where generated background migrations will be placed
|
8
|
+
# @return [String] defaults to "lib"
|
9
|
+
attr_accessor :migrations_path
|
10
|
+
|
7
11
|
# The module in which background migrations will be placed
|
8
12
|
# @return [String] defaults to "OnlineMigrations::BackgroundMigrations"
|
9
13
|
attr_accessor :migrations_module
|
@@ -75,6 +79,7 @@ module OnlineMigrations
|
|
75
79
|
attr_accessor :error_handler
|
76
80
|
|
77
81
|
def initialize
|
82
|
+
@migrations_path = "lib"
|
78
83
|
@migrations_module = "OnlineMigrations::BackgroundMigrations"
|
79
84
|
@batch_size = 20_000
|
80
85
|
@sub_batch_size = 1000
|
@@ -28,6 +28,8 @@ module OnlineMigrations
|
|
28
28
|
for_migration_name(migration_name).where("arguments = ?", arguments.to_json)
|
29
29
|
end
|
30
30
|
|
31
|
+
alias_attribute :name, :migration_name
|
32
|
+
|
31
33
|
enum status: STATUSES.index_with(&:to_s)
|
32
34
|
|
33
35
|
belongs_to :parent, class_name: name, optional: true
|
@@ -64,6 +66,7 @@ module OnlineMigrations
|
|
64
66
|
class_name = class_name.name if class_name.is_a?(Class)
|
65
67
|
write_attribute(:migration_name, self.class.normalize_migration_name(class_name))
|
66
68
|
end
|
69
|
+
alias name= migration_name=
|
67
70
|
|
68
71
|
def completed?
|
69
72
|
succeeded? || failed?
|
@@ -194,10 +197,8 @@ module OnlineMigrations
|
|
194
197
|
|
195
198
|
on_shard do
|
196
199
|
# rubocop:disable Lint/UnreachableLoop
|
197
|
-
iterator.each_batch(of: batch_size, column: batch_column_name, start: next_min_value) do |
|
198
|
-
|
199
|
-
max = relation.arel_table[batch_column_name].maximum
|
200
|
-
batch_range = relation.pick(min, max)
|
200
|
+
iterator.each_batch(of: batch_size, column: batch_column_name, start: next_min_value, finish: max_value) do |_relation, min_value, max_value|
|
201
|
+
batch_range = [min_value, max_value]
|
201
202
|
|
202
203
|
break
|
203
204
|
end
|
@@ -249,14 +250,10 @@ module OnlineMigrations
|
|
249
250
|
self.min_value = self.max_value = self.rows_count = -1 # not relevant
|
250
251
|
else
|
251
252
|
on_shard do
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
if min_value.nil?
|
257
|
-
# integer IDs minimum value is 1
|
258
|
-
self.min_value = self.max_value = 1
|
259
|
-
end
|
253
|
+
# Getting exact min/max values can be a very heavy operation
|
254
|
+
# and is not needed practically.
|
255
|
+
self.min_value ||= 1
|
256
|
+
self.max_value ||= migration_model.unscoped.maximum(batch_column_name) || self.min_value
|
260
257
|
|
261
258
|
count = migration_object.count
|
262
259
|
self.rows_count = count if count != :no_count
|
@@ -19,37 +19,42 @@ module OnlineMigrations
|
|
19
19
|
end
|
20
20
|
|
21
21
|
relation = apply_limits(self.relation, column, start, finish, order)
|
22
|
+
base_relation = relation.reselect(column).reorder(column => order)
|
22
23
|
|
23
|
-
|
24
|
-
.
|
25
|
-
|
26
|
-
|
27
|
-
start_row = base_relation.uncached { base_relation.first }
|
28
|
-
|
29
|
-
return if !start_row
|
24
|
+
start_id = start || begin
|
25
|
+
start_row = base_relation.uncached { base_relation.first }
|
26
|
+
start_row[column] if start_row
|
27
|
+
end
|
30
28
|
|
31
|
-
start_id = start_row[column]
|
32
29
|
arel_table = relation.arel_table
|
33
30
|
|
34
|
-
|
31
|
+
while start_id
|
35
32
|
if order == :asc
|
36
33
|
start_cond = arel_table[column].gteq(start_id)
|
37
34
|
else
|
38
35
|
start_cond = arel_table[column].lteq(start_id)
|
39
36
|
end
|
40
37
|
|
41
|
-
stop_row = base_relation.uncached do
|
38
|
+
last_row, stop_row = base_relation.uncached do
|
42
39
|
base_relation
|
43
40
|
.where(start_cond)
|
44
|
-
.offset(of)
|
45
|
-
.first
|
41
|
+
.offset(of - 1)
|
42
|
+
.first(2)
|
43
|
+
end
|
44
|
+
|
45
|
+
if last_row.nil?
|
46
|
+
# We are at the end of the table.
|
47
|
+
last_row, stop_row = base_relation.uncached do
|
48
|
+
base_relation
|
49
|
+
.where(start_cond)
|
50
|
+
.last(2)
|
51
|
+
end
|
46
52
|
end
|
47
53
|
|
48
54
|
batch_relation = relation.where(start_cond)
|
49
55
|
|
50
56
|
if stop_row
|
51
57
|
stop_id = stop_row[column]
|
52
|
-
start_id = stop_id
|
53
58
|
|
54
59
|
if order == :asc
|
55
60
|
stop_cond = arel_table[column].lt(stop_id)
|
@@ -64,10 +69,14 @@ module OnlineMigrations
|
|
64
69
|
# efficient UPDATE queries, hence we get rid of it.
|
65
70
|
batch_relation = batch_relation.except(:order)
|
66
71
|
|
72
|
+
last_id = (last_row && last_row[column]) || finish
|
73
|
+
|
67
74
|
# Retaining the results in the query cache would undermine the point of batching.
|
68
|
-
batch_relation.uncached { yield batch_relation,
|
75
|
+
batch_relation.uncached { yield batch_relation, start_id, last_id }
|
76
|
+
|
77
|
+
break if stop_row.nil?
|
69
78
|
|
70
|
-
|
79
|
+
start_id = stop_id
|
71
80
|
end
|
72
81
|
end
|
73
82
|
|
@@ -416,15 +416,8 @@ module OnlineMigrations
|
|
416
416
|
end
|
417
417
|
end
|
418
418
|
|
419
|
-
if index.name.include?(from_column)
|
420
|
-
name = index.name.gsub(from_column, to_column)
|
421
|
-
end
|
422
|
-
|
423
|
-
name = index_name(table_name, new_columns) if !name || name.length > max_identifier_length
|
424
|
-
|
425
419
|
options = {
|
426
420
|
unique: index.unique,
|
427
|
-
name: name,
|
428
421
|
length: index.lengths,
|
429
422
|
order: index.orders,
|
430
423
|
}
|
@@ -681,24 +681,20 @@ module OnlineMigrations
|
|
681
681
|
# @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_index
|
682
682
|
#
|
683
683
|
def add_index(table_name, column_name, **options)
|
684
|
-
|
685
|
-
|
686
|
-
__ensure_not_in_transaction! if algorithm == :concurrently
|
687
|
-
|
688
|
-
column_names = index_column_names(column_name || options[:column])
|
689
|
-
|
690
|
-
index_name = options[:name]
|
691
|
-
index_name ||= index_name(table_name, column_names)
|
684
|
+
__ensure_not_in_transaction! if options[:algorithm] == :concurrently
|
692
685
|
|
693
|
-
|
686
|
+
# Rewrite this with `IndexDefinition#defined_for?` when Active Record >= 7.1 is supported.
|
687
|
+
# See https://github.com/rails/rails/pull/45160.
|
688
|
+
index = indexes(table_name).find { |i| __index_defined_for?(i, column_name, **options) }
|
689
|
+
if index
|
694
690
|
schema = __schema_for_table(table_name)
|
695
691
|
|
696
|
-
if __index_valid?(
|
692
|
+
if __index_valid?(index.name, schema: schema)
|
697
693
|
Utils.say("Index was not created because it already exists.")
|
698
694
|
return
|
699
695
|
else
|
700
696
|
Utils.say("Recreating invalid index: table_name: #{table_name}, column_name: #{column_name}")
|
701
|
-
remove_index(table_name, column_name,
|
697
|
+
remove_index(table_name, column_name, **options)
|
702
698
|
end
|
703
699
|
end
|
704
700
|
|
@@ -706,7 +702,7 @@ module OnlineMigrations
|
|
706
702
|
# "CREATE INDEX CONCURRENTLY" requires a "SHARE UPDATE EXCLUSIVE" lock.
|
707
703
|
# It only conflicts with constraint validations, creating/removing indexes,
|
708
704
|
# and some other "ALTER TABLE"s.
|
709
|
-
super
|
705
|
+
super
|
710
706
|
else
|
711
707
|
OnlineMigrations.deprecator.warn(<<~MSG)
|
712
708
|
Running `add_index` without a statement timeout is deprecated.
|
@@ -716,7 +712,7 @@ module OnlineMigrations
|
|
716
712
|
MSG
|
717
713
|
|
718
714
|
disable_statement_timeout do
|
719
|
-
super
|
715
|
+
super
|
720
716
|
end
|
721
717
|
end
|
722
718
|
end
|
@@ -944,6 +940,17 @@ module OnlineMigrations
|
|
944
940
|
end
|
945
941
|
end
|
946
942
|
|
943
|
+
# Will not be needed for Active Record >= 7.1
|
944
|
+
def __index_defined_for?(index, columns = nil, name: nil, unique: nil, valid: nil, include: nil, nulls_not_distinct: nil, **options)
|
945
|
+
columns = options[:column] if columns.blank?
|
946
|
+
(columns.nil? || Array(index.columns) == Array(columns).map(&:to_s)) &&
|
947
|
+
(name.nil? || index.name == name.to_s) &&
|
948
|
+
(unique.nil? || index.unique == unique) &&
|
949
|
+
(valid.nil? || index.valid == valid) &&
|
950
|
+
(include.nil? || Array(index.include) == Array(include).map(&:to_s)) &&
|
951
|
+
(nulls_not_distinct.nil? || index.nulls_not_distinct == nulls_not_distinct)
|
952
|
+
end
|
953
|
+
|
947
954
|
def __not_null_constraint_exists?(table_name, column_name, name: nil)
|
948
955
|
name ||= __not_null_constraint_name(table_name, column_name)
|
949
956
|
__check_constraint_exists?(table_name, name: name)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: online_migrations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- fatkodima
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-01-
|
11
|
+
date: 2024-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|