online_migrations 0.11.0 → 0.12.0
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 +15 -0
- data/docs/configuring.md +21 -10
- data/lib/generators/online_migrations/templates/initializer.rb.tt +9 -2
- data/lib/online_migrations/background_migrations/config.rb +1 -1
- data/lib/online_migrations/background_migrations/migration.rb +27 -35
- data/lib/online_migrations/background_migrations/migration_helpers.rb +37 -7
- data/lib/online_migrations/background_migrations/migration_runner.rb +9 -5
- data/lib/online_migrations/command_checker.rb +11 -0
- data/lib/online_migrations/config.rb +7 -1
- data/lib/online_migrations/lock_retrier.rb +4 -6
- data/lib/online_migrations/schema_statements.rb +72 -33
- data/lib/online_migrations/utils.rb +15 -1
- data/lib/online_migrations/version.rb +1 -1
- data/lib/online_migrations.rb +21 -11
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a5db4da5657a6887113af46baa5c5ec9a6906ba4f8e3d33afc3d81f378f9b5ae
|
4
|
+
data.tar.gz: 0acf2706e6b453055d41f338908323a0672066b6e742724b8a6d588e969ca05f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a1b97dceb858d5c6ebd6ddccab4ea0b3ed8d257074f1e7f6562f101b3ae002ac19576053a44fe765624350c40d6e269d753fd24be5b5419474036a308b5cce5
|
7
|
+
data.tar.gz: 9b6a3ece26c9e3e679c5d8d0f827eda7aa10b591242440a18d2a0fb18aa8f16b16935db6654859aab9b6f5008160e117c2eb32e98571261127d1a6ac1e936f2c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
## master (unreleased)
|
2
2
|
|
3
|
+
## 0.12.0 (2024-01-18)
|
4
|
+
|
5
|
+
- Require passing model name for background migration helpers when using multiple databases
|
6
|
+
- Add `statement_timeout` configuration option
|
7
|
+
|
8
|
+
- Make `lock_timeout` argument optional for `config.lock_retrier`
|
9
|
+
|
10
|
+
This way, a default lock timeout value will be used (configured in `database.yml` or for the database user).
|
11
|
+
|
12
|
+
- Fix a bug that can lead to unfinished children of a sharded background migration
|
13
|
+
|
14
|
+
## 0.11.1 (2024-01-11)
|
15
|
+
|
16
|
+
- Fix calculation of batch ranges for sharded background migrations
|
17
|
+
|
3
18
|
## 0.11.0 (2024-01-09)
|
4
19
|
|
5
20
|
- Support sharding for background migrations
|
data/docs/configuring.md
CHANGED
@@ -59,22 +59,33 @@ Check the [source code](https://github.com/fatkodima/online_migrations/blob/mast
|
|
59
59
|
## Migration Timeouts
|
60
60
|
|
61
61
|
It’s extremely important to set a short lock timeout for migrations. This way, if a migration can't acquire a lock in a timely manner, other statements won't be stuck behind it.
|
62
|
+
We also recommend setting a long statement timeout so migrations can run for a while.
|
62
63
|
|
63
|
-
|
64
|
+
You can configure a statement timeout for migrations via:
|
64
65
|
|
65
|
-
```
|
66
|
-
|
67
|
-
connect_timeout: 5
|
68
|
-
variables:
|
69
|
-
lock_timeout: 10s
|
70
|
-
statement_timeout: 15s
|
66
|
+
```ruby
|
67
|
+
config.statement_timeout = 1.hour
|
71
68
|
```
|
72
69
|
|
70
|
+
and a lock timeout for migrations can be configured via the `lock_retrier`.
|
71
|
+
|
73
72
|
Or set the timeouts directly on the database user that runs migrations:
|
74
73
|
|
75
74
|
```sql
|
76
75
|
ALTER ROLE myuser SET lock_timeout = '10s';
|
77
|
-
ALTER ROLE myuser SET statement_timeout = '
|
76
|
+
ALTER ROLE myuser SET statement_timeout = '1h';
|
77
|
+
```
|
78
|
+
|
79
|
+
## App Timeouts
|
80
|
+
|
81
|
+
We recommend adding timeouts to `config/database.yml` to prevent connections from hanging and individual queries from taking up too many resources in controllers, jobs, the Rails console, and other places.
|
82
|
+
|
83
|
+
```yml
|
84
|
+
production:
|
85
|
+
connect_timeout: 5
|
86
|
+
variables:
|
87
|
+
lock_timeout: 10s
|
88
|
+
statement_timeout: 15s
|
78
89
|
```
|
79
90
|
|
80
91
|
## Lock Timeout Retries
|
@@ -86,7 +97,7 @@ config.lock_retrier = OnlineMigrations::ExponentialLockRetrier.new(
|
|
86
97
|
attempts: 30, # attempt 30 retries
|
87
98
|
base_delay: 0.01.seconds, # starting with delay of 10ms between each unsuccessful try, increasing exponentially
|
88
99
|
max_delay: 1.minute, # maximum delay is 1 minute
|
89
|
-
lock_timeout: 0.2.seconds # and 200ms set as lock timeout for each try
|
100
|
+
lock_timeout: 0.2.seconds # and 200ms set as lock timeout for each try. Remove this line to use a default lock timeout.
|
90
101
|
)
|
91
102
|
```
|
92
103
|
|
@@ -192,7 +203,7 @@ To enable verbose sql logs:
|
|
192
203
|
config.verbose_sql_logs = true
|
193
204
|
```
|
194
205
|
|
195
|
-
This feature is enabled by default in a production Rails
|
206
|
+
This feature is enabled by default in a staging and production Rails environments. You can override this setting via `ONLINE_MIGRATIONS_VERBOSE_SQL_LOGS` environment variable.
|
196
207
|
|
197
208
|
## Analyze Tables
|
198
209
|
|
@@ -4,6 +4,9 @@ OnlineMigrations.configure do |config|
|
|
4
4
|
# Configure the migration version starting after which checks are performed.
|
5
5
|
# config.start_after = <%= start_after %>
|
6
6
|
|
7
|
+
# Configure statement timeout used for migrations.
|
8
|
+
config.statement_timeout = 1.hour
|
9
|
+
|
7
10
|
# Set the version of the production database so the right checks are run in development.
|
8
11
|
# config.target_version = 10
|
9
12
|
|
@@ -48,7 +51,7 @@ OnlineMigrations.configure do |config|
|
|
48
51
|
# a better grasp of what is going on for high-level statements like add_column_with_default.
|
49
52
|
#
|
50
53
|
# Note: It can be overridden by `ONLINE_MIGRATIONS_VERBOSE_SQL_LOGS` environment variable.
|
51
|
-
config.verbose_sql_logs = defined?(Rails) && Rails.env.production?
|
54
|
+
config.verbose_sql_logs = defined?(Rails.env) && (Rails.env.production? || Rails.env.staging?)
|
52
55
|
|
53
56
|
# Lock retries.
|
54
57
|
# Configure your custom lock retrier (see LockRetrier).
|
@@ -57,7 +60,7 @@ OnlineMigrations.configure do |config|
|
|
57
60
|
attempts: 30, # attempt 30 retries
|
58
61
|
base_delay: 0.01.seconds, # starting with delay of 10ms between each unsuccessful try, increasing exponentially
|
59
62
|
max_delay: 1.minute, # up to the maximum delay of 1 minute
|
60
|
-
lock_timeout: 0.2.seconds # and 200ms set as lock timeout for each try
|
63
|
+
lock_timeout: 0.2.seconds # and 200ms set as lock timeout for each try. Remove this line to use a default lock timeout.
|
61
64
|
)
|
62
65
|
|
63
66
|
# Configure tables that are in the process of being renamed.
|
@@ -75,6 +78,10 @@ OnlineMigrations.configure do |config|
|
|
75
78
|
# end
|
76
79
|
|
77
80
|
# ==> Background migrations configuration
|
81
|
+
|
82
|
+
# The module in which background migrations will be placed.
|
83
|
+
# config.background_migrations.migrations_module = "OnlineMigrations::BackgroundMigrations"
|
84
|
+
|
78
85
|
# The number of rows to process in a single background migration run.
|
79
86
|
# config.background_migrations.batch_size = 20_000
|
80
87
|
|
@@ -4,7 +4,7 @@ module OnlineMigrations
|
|
4
4
|
module BackgroundMigrations
|
5
5
|
# Class representing configuration options for background migrations.
|
6
6
|
class Config
|
7
|
-
# The module
|
7
|
+
# The module in which background migrations will be placed
|
8
8
|
# @return [String] defaults to "OnlineMigrations::BackgroundMigrations"
|
9
9
|
attr_accessor :migrations_module
|
10
10
|
|
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
module OnlineMigrations
|
4
4
|
module BackgroundMigrations
|
5
|
+
# Class representing background data migration.
|
6
|
+
#
|
7
|
+
# @note The records of this class should not be created manually, but via
|
8
|
+
# `enqueue_background_migration` helper inside migrations.
|
9
|
+
#
|
5
10
|
class Migration < ApplicationRecord
|
6
11
|
STATUSES = [
|
7
12
|
:enqueued, # The migration has been enqueued by the user.
|
@@ -26,8 +31,8 @@ module OnlineMigrations
|
|
26
31
|
enum status: STATUSES.index_with(&:to_s)
|
27
32
|
|
28
33
|
belongs_to :parent, class_name: name, optional: true
|
29
|
-
has_many :children, class_name: name, foreign_key: :parent_id
|
30
|
-
has_many :migration_jobs
|
34
|
+
has_many :children, class_name: name, foreign_key: :parent_id, dependent: :delete_all
|
35
|
+
has_many :migration_jobs, dependent: :delete_all
|
31
36
|
|
32
37
|
validates :migration_name, :batch_column_name, presence: true
|
33
38
|
|
@@ -47,7 +52,6 @@ module OnlineMigrations
|
|
47
52
|
validates_with MigrationStatusValidator, on: :update
|
48
53
|
|
49
54
|
before_validation :set_defaults
|
50
|
-
before_create :create_child_migrations, if: :composite?
|
51
55
|
before_update :copy_attributes_to_children, if: :composite?
|
52
56
|
|
53
57
|
# @private
|
@@ -57,6 +61,7 @@ module OnlineMigrations
|
|
57
61
|
end
|
58
62
|
|
59
63
|
def migration_name=(class_name)
|
64
|
+
class_name = class_name.name if class_name.is_a?(Class)
|
60
65
|
write_attribute(:migration_name, self.class.normalize_migration_name(class_name))
|
61
66
|
end
|
62
67
|
|
@@ -102,9 +107,15 @@ module OnlineMigrations
|
|
102
107
|
if succeeded?
|
103
108
|
100.0
|
104
109
|
elsif composite?
|
105
|
-
|
106
|
-
if
|
107
|
-
|
110
|
+
rows_counts = children.to_a.pluck(:rows_count)
|
111
|
+
if rows_counts.none?(nil)
|
112
|
+
total_rows_count = rows_counts.sum
|
113
|
+
|
114
|
+
progresses = children.map do |child|
|
115
|
+
child.progress * child.rows_count / total_rows_count # weighted progress
|
116
|
+
end
|
117
|
+
|
118
|
+
progresses.sum.round(2)
|
108
119
|
end
|
109
120
|
elsif rows_count
|
110
121
|
jobs_rows_count = migration_jobs.succeeded.sum(:batch_size)
|
@@ -181,15 +192,17 @@ module OnlineMigrations
|
|
181
192
|
iterator = BatchIterator.new(migration_relation)
|
182
193
|
batch_range = nil
|
183
194
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
195
|
+
on_shard do
|
196
|
+
# rubocop:disable Lint/UnreachableLoop
|
197
|
+
iterator.each_batch(of: batch_size, column: batch_column_name, start: next_min_value) do |relation|
|
198
|
+
min = relation.arel_table[batch_column_name].minimum
|
199
|
+
max = relation.arel_table[batch_column_name].maximum
|
200
|
+
batch_range = relation.pick(min, max)
|
189
201
|
|
190
|
-
|
202
|
+
break
|
203
|
+
end
|
204
|
+
# rubocop:enable Lint/UnreachableLoop
|
191
205
|
end
|
192
|
-
# rubocop:enable Lint/UnreachableLoop
|
193
206
|
|
194
207
|
return if batch_range.nil?
|
195
208
|
|
@@ -201,10 +214,6 @@ module OnlineMigrations
|
|
201
214
|
[min_value, max_value]
|
202
215
|
end
|
203
216
|
|
204
|
-
protected
|
205
|
-
attr_accessor :child
|
206
|
-
alias child? child
|
207
|
-
|
208
217
|
private
|
209
218
|
def validate_batch_column_values
|
210
219
|
if max_value.to_i < min_value.to_i
|
@@ -234,11 +243,6 @@ module OnlineMigrations
|
|
234
243
|
|
235
244
|
def set_defaults
|
236
245
|
if migration_relation.is_a?(ActiveRecord::Relation)
|
237
|
-
if !child?
|
238
|
-
shards = Utils.shard_names(migration_model)
|
239
|
-
self.composite = shards.size > 1
|
240
|
-
end
|
241
|
-
|
242
246
|
self.batch_column_name ||= migration_relation.primary_key
|
243
247
|
|
244
248
|
if composite?
|
@@ -268,25 +272,13 @@ module OnlineMigrations
|
|
268
272
|
self.batch_max_attempts ||= config.batch_max_attempts
|
269
273
|
end
|
270
274
|
|
271
|
-
def create_child_migrations
|
272
|
-
shards = Utils.shard_names(migration_model)
|
273
|
-
|
274
|
-
children = shards.map do |shard|
|
275
|
-
child = Migration.new(migration_name: migration_name, arguments: arguments, shard: shard)
|
276
|
-
child.child = true
|
277
|
-
child
|
278
|
-
end
|
279
|
-
|
280
|
-
self.children = children
|
281
|
-
end
|
282
|
-
|
283
275
|
def copy_attributes_to_children
|
284
276
|
attributes = [:batch_size, :sub_batch_size, :batch_pause, :sub_batch_pause_ms, :batch_max_attempts]
|
285
277
|
updates = {}
|
286
278
|
attributes.each do |attribute|
|
287
279
|
updates[attribute] = read_attribute(attribute) if attribute_changed?(attribute)
|
288
280
|
end
|
289
|
-
children.update_all(updates) if updates.any?
|
281
|
+
children.active.update_all(updates) if updates.any?
|
290
282
|
end
|
291
283
|
|
292
284
|
def next_min_value
|
@@ -42,6 +42,10 @@ module OnlineMigrations
|
|
42
42
|
# @see #backfill_column_in_background
|
43
43
|
#
|
44
44
|
def backfill_columns_in_background(table_name, updates, model_name: nil, **options)
|
45
|
+
if model_name.nil? && Utils.multiple_databases?
|
46
|
+
raise ArgumentError, "You must pass a :model_name when using multiple databases."
|
47
|
+
end
|
48
|
+
|
45
49
|
model_name = model_name.name if model_name.is_a?(Class)
|
46
50
|
|
47
51
|
enqueue_background_migration(
|
@@ -99,6 +103,10 @@ module OnlineMigrations
|
|
99
103
|
#
|
100
104
|
def backfill_columns_for_type_change_in_background(table_name, *column_names, model_name: nil,
|
101
105
|
type_cast_functions: {}, **options)
|
106
|
+
if model_name.nil? && Utils.multiple_databases?
|
107
|
+
raise ArgumentError, "You must pass a :model_name when using multiple databases."
|
108
|
+
end
|
109
|
+
|
102
110
|
tmp_columns = column_names.map { |column_name| "#{column_name}_for_type_change" }
|
103
111
|
model_name = model_name.name if model_name.is_a?(Class)
|
104
112
|
|
@@ -153,6 +161,10 @@ module OnlineMigrations
|
|
153
161
|
# @see #copy_column_in_background
|
154
162
|
#
|
155
163
|
def copy_columns_in_background(table_name, copy_from, copy_to, model_name: nil, type_cast_functions: {}, **options)
|
164
|
+
if model_name.nil? && Utils.multiple_databases?
|
165
|
+
raise ArgumentError, "You must pass a :model_name when using multiple databases."
|
166
|
+
end
|
167
|
+
|
156
168
|
model_name = model_name.name if model_name.is_a?(Class)
|
157
169
|
|
158
170
|
enqueue_background_migration(
|
@@ -358,23 +370,41 @@ module OnlineMigrations
|
|
358
370
|
# in development and test environments
|
359
371
|
#
|
360
372
|
def enqueue_background_migration(migration_name, *arguments, **options)
|
373
|
+
migration = create_background_migration(migration_name, *arguments, **options)
|
374
|
+
|
375
|
+
# For convenience in dev/test environments
|
376
|
+
if Utils.developer_env?
|
377
|
+
runner = MigrationRunner.new(migration)
|
378
|
+
runner.run_all_migration_jobs
|
379
|
+
end
|
380
|
+
|
381
|
+
migration
|
382
|
+
end
|
383
|
+
|
384
|
+
# @private
|
385
|
+
def create_background_migration(migration_name, *arguments, **options)
|
361
386
|
options.assert_valid_keys(:batch_column_name, :min_value, :max_value, :batch_size, :sub_batch_size,
|
362
387
|
:batch_pause, :sub_batch_pause_ms, :batch_max_attempts)
|
363
388
|
|
364
|
-
|
365
|
-
|
366
|
-
migration = Migration.create!(
|
389
|
+
migration = Migration.new(
|
367
390
|
migration_name: migration_name,
|
368
391
|
arguments: arguments,
|
369
392
|
**options
|
370
393
|
)
|
371
394
|
|
372
|
-
|
373
|
-
if
|
374
|
-
|
375
|
-
|
395
|
+
shards = Utils.shard_names(migration.migration_model)
|
396
|
+
if shards.size > 1
|
397
|
+
migration.children = shards.map do |shard|
|
398
|
+
child = migration.dup
|
399
|
+
child.shard = shard
|
400
|
+
child
|
401
|
+
end
|
402
|
+
|
403
|
+
migration.composite = true
|
376
404
|
end
|
377
405
|
|
406
|
+
# This will save all the records using a transaction.
|
407
|
+
migration.save!
|
378
408
|
migration
|
379
409
|
end
|
380
410
|
end
|
@@ -14,10 +14,7 @@ module OnlineMigrations
|
|
14
14
|
def run_migration_job
|
15
15
|
raise "Should not be called on a composite (with sharding) migration" if migration.composite?
|
16
16
|
|
17
|
-
if migration.enqueued?
|
18
|
-
migration.running!
|
19
|
-
migration.parent.running! if migration.parent && migration.parent.enqueued?
|
20
|
-
end
|
17
|
+
mark_as_running if migration.enqueued?
|
21
18
|
migration_payload = notifications_payload(migration)
|
22
19
|
|
23
20
|
if !migration.migration_jobs.exists?
|
@@ -57,7 +54,7 @@ module OnlineMigrations
|
|
57
54
|
raise "This method is not intended for use in production environments" if !Utils.developer_env?
|
58
55
|
return if migration.completed?
|
59
56
|
|
60
|
-
|
57
|
+
mark_as_running
|
61
58
|
|
62
59
|
if migration.composite?
|
63
60
|
migration.children.each do |child_migration|
|
@@ -96,6 +93,13 @@ module OnlineMigrations
|
|
96
93
|
end
|
97
94
|
|
98
95
|
private
|
96
|
+
def mark_as_running
|
97
|
+
Migration.transaction do
|
98
|
+
migration.running!
|
99
|
+
migration.parent.running! if migration.parent && migration.parent.enqueued?
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
99
103
|
def should_throttle?
|
100
104
|
::OnlineMigrations.config.background_migrations.throttler.call
|
101
105
|
end
|
@@ -31,6 +31,7 @@ module OnlineMigrations
|
|
31
31
|
|
32
32
|
def check(command, *args, &block)
|
33
33
|
check_database_version
|
34
|
+
set_statement_timeout
|
34
35
|
check_lock_timeout
|
35
36
|
|
36
37
|
if !safe?
|
@@ -98,6 +99,16 @@ module OnlineMigrations
|
|
98
99
|
@database_version_checked = true
|
99
100
|
end
|
100
101
|
|
102
|
+
def set_statement_timeout
|
103
|
+
if !@statement_timeout_set
|
104
|
+
if (statement_timeout = OnlineMigrations.config.statement_timeout)
|
105
|
+
# TODO: inline this method call after deprecated `disable_statement_timeout` method removal.
|
106
|
+
connection.__set_statement_timeout(statement_timeout)
|
107
|
+
end
|
108
|
+
@statement_timeout_set = true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
101
112
|
def check_lock_timeout
|
102
113
|
limit = OnlineMigrations.config.lock_timeout_limit
|
103
114
|
|
@@ -35,6 +35,12 @@ module OnlineMigrations
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
+
# Statement timeout used for migrations (in seconds)
|
39
|
+
#
|
40
|
+
# @return [Numeric]
|
41
|
+
#
|
42
|
+
attr_accessor :statement_timeout
|
43
|
+
|
38
44
|
# Set the database version against which the checks will be performed
|
39
45
|
#
|
40
46
|
# If your development database version is different from production, you can specify
|
@@ -158,7 +164,7 @@ module OnlineMigrations
|
|
158
164
|
# migration failure in production. This is also useful in development to get
|
159
165
|
# a better grasp of what is going on for high-level statements like add_column_with_default.
|
160
166
|
#
|
161
|
-
# This feature is enabled by default in a production Rails
|
167
|
+
# This feature is enabled by default in a staging and production Rails environments.
|
162
168
|
# @return [Boolean]
|
163
169
|
#
|
164
170
|
# @note: It can be overridden by `ONLINE_MIGRATIONS_VERBOSE_SQL_LOGS` environment variable.
|
@@ -60,9 +60,7 @@ module OnlineMigrations
|
|
60
60
|
#
|
61
61
|
# @param _attempt [Integer] attempt number
|
62
62
|
#
|
63
|
-
def lock_timeout(_attempt)
|
64
|
-
raise NotImplementedError
|
65
|
-
end
|
63
|
+
def lock_timeout(_attempt); end
|
66
64
|
|
67
65
|
# Returns sleep time after unsuccessful lock attempt (in seconds)
|
68
66
|
#
|
@@ -143,9 +141,9 @@ module OnlineMigrations
|
|
143
141
|
#
|
144
142
|
# @param attempts [Integer] Maximum number of attempts
|
145
143
|
# @param delay [Numeric] Sleep time after unsuccessful lock attempt (in seconds)
|
146
|
-
# @param lock_timeout [Numeric] Database lock timeout value (in seconds)
|
144
|
+
# @param lock_timeout [Numeric, nil] Database lock timeout value (in seconds)
|
147
145
|
#
|
148
|
-
def initialize(attempts:, delay:, lock_timeout:)
|
146
|
+
def initialize(attempts:, delay:, lock_timeout: nil)
|
149
147
|
super()
|
150
148
|
@attempts = attempts
|
151
149
|
@delay = delay
|
@@ -196,7 +194,7 @@ module OnlineMigrations
|
|
196
194
|
# @param max_delay [Numeric] Maximum sleep time after unsuccessful lock attempt (in seconds)
|
197
195
|
# @param lock_timeout [Numeric] Database lock timeout value (in seconds)
|
198
196
|
#
|
199
|
-
def initialize(attempts:, base_delay:, max_delay:, lock_timeout:)
|
197
|
+
def initialize(attempts:, base_delay:, max_delay:, lock_timeout: nil)
|
200
198
|
super()
|
201
199
|
@attempts = attempts
|
202
200
|
@base_delay = base_delay
|
@@ -680,7 +680,7 @@ module OnlineMigrations
|
|
680
680
|
#
|
681
681
|
# @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_index
|
682
682
|
#
|
683
|
-
def add_index(table_name, column_name, options
|
683
|
+
def add_index(table_name, column_name, **options)
|
684
684
|
algorithm = options[:algorithm]
|
685
685
|
|
686
686
|
__ensure_not_in_transaction! if algorithm == :concurrently
|
@@ -694,8 +694,7 @@ module OnlineMigrations
|
|
694
694
|
schema = __schema_for_table(table_name)
|
695
695
|
|
696
696
|
if __index_valid?(index_name, schema: schema)
|
697
|
-
Utils.say("Index was not created because it already exists
|
698
|
-
"or similar): table_name: #{table_name}, column_name: #{column_name}")
|
697
|
+
Utils.say("Index was not created because it already exists.")
|
699
698
|
return
|
700
699
|
else
|
701
700
|
Utils.say("Recreating invalid index: table_name: #{table_name}, column_name: #{column_name}")
|
@@ -703,11 +702,22 @@ module OnlineMigrations
|
|
703
702
|
end
|
704
703
|
end
|
705
704
|
|
706
|
-
|
705
|
+
if OnlineMigrations.config.statement_timeout
|
707
706
|
# "CREATE INDEX CONCURRENTLY" requires a "SHARE UPDATE EXCLUSIVE" lock.
|
708
707
|
# It only conflicts with constraint validations, creating/removing indexes,
|
709
708
|
# and some other "ALTER TABLE"s.
|
710
709
|
super(table_name, column_name, **options.merge(name: index_name))
|
710
|
+
else
|
711
|
+
OnlineMigrations.deprecator.warn(<<~MSG)
|
712
|
+
Running `add_index` without a statement timeout is deprecated.
|
713
|
+
Configure an explicit statement timeout in the initializer file via `config.statement_timeout`
|
714
|
+
or the default database statement timeout will be used.
|
715
|
+
Example, `config.statement_timeout = 1.hour`.
|
716
|
+
MSG
|
717
|
+
|
718
|
+
disable_statement_timeout do
|
719
|
+
super(table_name, column_name, **options.merge(name: index_name))
|
720
|
+
end
|
711
721
|
end
|
712
722
|
end
|
713
723
|
|
@@ -716,23 +726,32 @@ module OnlineMigrations
|
|
716
726
|
# @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-remove_index
|
717
727
|
#
|
718
728
|
def remove_index(table_name, column_name = nil, **options)
|
719
|
-
|
729
|
+
if column_name.blank? && options[:column].blank? && options[:name].blank?
|
730
|
+
raise ArgumentError, "No name or columns specified"
|
731
|
+
end
|
720
732
|
|
721
|
-
__ensure_not_in_transaction! if algorithm == :concurrently
|
733
|
+
__ensure_not_in_transaction! if options[:algorithm] == :concurrently
|
722
734
|
|
723
|
-
|
724
|
-
|
725
|
-
if index_exists?(table_name, column_names, **options)
|
726
|
-
disable_statement_timeout do
|
735
|
+
if index_exists?(table_name, column_name, **options)
|
736
|
+
if OnlineMigrations.config.statement_timeout
|
727
737
|
# "DROP INDEX CONCURRENTLY" requires a "SHARE UPDATE EXCLUSIVE" lock.
|
728
738
|
# It only conflicts with constraint validations, other creating/removing indexes,
|
729
739
|
# and some "ALTER TABLE"s.
|
740
|
+
super(table_name, column_name, **options)
|
741
|
+
else
|
742
|
+
OnlineMigrations.deprecator.warn(<<~MSG)
|
743
|
+
Running `remove_index` without a statement timeout is deprecated.
|
744
|
+
Configure an explicit statement timeout in the initializer file via `config.statement_timeout`
|
745
|
+
or the default database statement timeout will be used.
|
746
|
+
Example, `config.statement_timeout = 1.hour`.
|
747
|
+
MSG
|
730
748
|
|
731
|
-
|
749
|
+
disable_statement_timeout do
|
750
|
+
super(table_name, column_name, **options)
|
751
|
+
end
|
732
752
|
end
|
733
753
|
else
|
734
|
-
Utils.say("Index was not removed because it does not exist
|
735
|
-
"or similar): table_name: #{table_name}, column_name: #{column_names}")
|
754
|
+
Utils.say("Index was not removed because it does not exist.")
|
736
755
|
end
|
737
756
|
end
|
738
757
|
|
@@ -778,11 +797,22 @@ module OnlineMigrations
|
|
778
797
|
# Skip costly operation if already validated.
|
779
798
|
return if foreign_key.validated?
|
780
799
|
|
781
|
-
|
800
|
+
if OnlineMigrations.config.statement_timeout
|
782
801
|
# "VALIDATE CONSTRAINT" requires a "SHARE UPDATE EXCLUSIVE" lock.
|
783
802
|
# It only conflicts with other validations, creating/removing indexes,
|
784
803
|
# and some other "ALTER TABLE"s.
|
785
804
|
super
|
805
|
+
else
|
806
|
+
OnlineMigrations.deprecator.warn(<<~MSG)
|
807
|
+
Running `validate_foreign_key` without a statement timeout is deprecated.
|
808
|
+
Configure an explicit statement timeout in the initializer file via `config.statement_timeout`
|
809
|
+
or the default database statement timeout will be used.
|
810
|
+
Example, `config.statement_timeout = 1.hour`.
|
811
|
+
MSG
|
812
|
+
|
813
|
+
disable_statement_timeout do
|
814
|
+
super
|
815
|
+
end
|
786
816
|
end
|
787
817
|
end
|
788
818
|
|
@@ -811,11 +841,22 @@ module OnlineMigrations
|
|
811
841
|
# Skip costly operation if already validated.
|
812
842
|
return if check_constraint.validated?
|
813
843
|
|
814
|
-
|
844
|
+
if OnlineMigrations.config.statement_timeout
|
815
845
|
# "VALIDATE CONSTRAINT" requires a "SHARE UPDATE EXCLUSIVE" lock.
|
816
846
|
# It only conflicts with other validations, creating/removing indexes,
|
817
847
|
# and some other "ALTER TABLE"s.
|
818
848
|
super
|
849
|
+
else
|
850
|
+
OnlineMigrations.deprecator.warn(<<~MSG)
|
851
|
+
Running `validate_check_constraint` without a statement timeout is deprecated.
|
852
|
+
Configure an explicit statement timeout in the initializer file via `config.statement_timeout`
|
853
|
+
or the default database statement timeout will be used.
|
854
|
+
Example, `config.statement_timeout = 1.hour`.
|
855
|
+
MSG
|
856
|
+
|
857
|
+
disable_statement_timeout do
|
858
|
+
super
|
859
|
+
end
|
819
860
|
end
|
820
861
|
end
|
821
862
|
|
@@ -855,28 +896,26 @@ module OnlineMigrations
|
|
855
896
|
end
|
856
897
|
end
|
857
898
|
|
858
|
-
#
|
859
|
-
#
|
860
|
-
# Long-running migrations may take more than the timeout allowed by the database.
|
861
|
-
# Disable the session's statement timeout to ensure migrations don't get killed prematurely.
|
862
|
-
#
|
863
|
-
# Statement timeouts are already disabled in `add_index`, `remove_index`,
|
864
|
-
# `validate_foreign_key`, and `validate_check_constraint` helpers.
|
865
|
-
#
|
866
|
-
# @return [void]
|
867
|
-
#
|
868
|
-
# @example
|
869
|
-
# disable_statement_timeout do
|
870
|
-
# add_index(:users, :email, unique: true, algorithm: :concurrently)
|
871
|
-
# end
|
872
|
-
#
|
899
|
+
# @private
|
873
900
|
def disable_statement_timeout
|
874
|
-
|
875
|
-
|
901
|
+
OnlineMigrations.deprecator.warn(<<~MSG)
|
902
|
+
`disable_statement_timeout` is deprecated and will be removed. Configure an explicit
|
903
|
+
statement timeout in the initializer file via `config.statement_timeout` or the default
|
904
|
+
database statement timeout will be used. Example, `config.statement_timeout = 1.hour`.
|
905
|
+
MSG
|
876
906
|
|
907
|
+
prev_value = select_value("SHOW statement_timeout")
|
908
|
+
__set_statement_timeout(0)
|
877
909
|
yield
|
878
910
|
ensure
|
879
|
-
|
911
|
+
__set_statement_timeout(prev_value)
|
912
|
+
end
|
913
|
+
|
914
|
+
# @private
|
915
|
+
def __set_statement_timeout(timeout)
|
916
|
+
# use ceil to prevent no timeout for values under 1 ms
|
917
|
+
timeout = (timeout.to_f * 1000).ceil if !timeout.is_a?(String)
|
918
|
+
execute("SET statement_timeout TO #{quote(timeout)}")
|
880
919
|
end
|
881
920
|
|
882
921
|
# @private
|
@@ -10,8 +10,17 @@ module OnlineMigrations
|
|
10
10
|
ActiveRecord.version.to_s.to_f
|
11
11
|
end
|
12
12
|
|
13
|
+
def env
|
14
|
+
if defined?(Rails.env)
|
15
|
+
Rails.env
|
16
|
+
else
|
17
|
+
# default to production for safety
|
18
|
+
ENV["RACK_ENV"] || "production"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
13
22
|
def developer_env?
|
14
|
-
|
23
|
+
env == "development" || env == "test"
|
15
24
|
end
|
16
25
|
|
17
26
|
def say(message)
|
@@ -137,6 +146,11 @@ module OnlineMigrations
|
|
137
146
|
return pool_manager.shard_names.uniq if pool_manager
|
138
147
|
end
|
139
148
|
end
|
149
|
+
|
150
|
+
def multiple_databases?
|
151
|
+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: env)
|
152
|
+
db_config.reject(&:replica?).size > 1
|
153
|
+
end
|
140
154
|
end
|
141
155
|
end
|
142
156
|
end
|
data/lib/online_migrations.rb
CHANGED
@@ -1,7 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_record"
|
4
|
+
|
4
5
|
require "online_migrations/version"
|
6
|
+
require "online_migrations/utils"
|
7
|
+
require "online_migrations/change_column_type_helpers"
|
8
|
+
require "online_migrations/background_migrations/migration_helpers"
|
9
|
+
require "online_migrations/schema_statements"
|
10
|
+
require "online_migrations/migration"
|
11
|
+
require "online_migrations/migrator"
|
12
|
+
require "online_migrations/schema_dumper"
|
13
|
+
require "online_migrations/database_tasks"
|
14
|
+
require "online_migrations/command_recorder"
|
15
|
+
require "online_migrations/error_messages"
|
16
|
+
require "online_migrations/config"
|
5
17
|
|
6
18
|
module OnlineMigrations
|
7
19
|
class Error < StandardError; end
|
@@ -9,15 +21,8 @@ module OnlineMigrations
|
|
9
21
|
|
10
22
|
extend ActiveSupport::Autoload
|
11
23
|
|
12
|
-
autoload :Utils
|
13
|
-
autoload :ErrorMessages
|
14
|
-
autoload :Config
|
15
24
|
autoload :BatchIterator
|
16
25
|
autoload :VerboseSqlLogs
|
17
|
-
autoload :Migration
|
18
|
-
autoload :Migrator
|
19
|
-
autoload :SchemaDumper
|
20
|
-
autoload :DatabaseTasks
|
21
26
|
autoload :ForeignKeysCollector
|
22
27
|
autoload :IndexDefinition
|
23
28
|
autoload :IndexesCollector
|
@@ -36,10 +41,7 @@ module OnlineMigrations
|
|
36
41
|
autoload :NullLockRetrier
|
37
42
|
end
|
38
43
|
|
39
|
-
autoload :CommandRecorder
|
40
44
|
autoload :CopyTrigger
|
41
|
-
autoload :ChangeColumnTypeHelpers
|
42
|
-
autoload :SchemaStatements
|
43
45
|
|
44
46
|
module BackgroundMigrations
|
45
47
|
extend ActiveSupport::Autoload
|
@@ -59,7 +61,6 @@ module OnlineMigrations
|
|
59
61
|
autoload :Migration
|
60
62
|
autoload :MigrationJobRunner
|
61
63
|
autoload :MigrationRunner
|
62
|
-
autoload :MigrationHelpers
|
63
64
|
autoload :Scheduler
|
64
65
|
end
|
65
66
|
|
@@ -80,6 +81,15 @@ module OnlineMigrations
|
|
80
81
|
BackgroundMigrations::Scheduler.run
|
81
82
|
end
|
82
83
|
|
84
|
+
def deprecator
|
85
|
+
@deprecator ||=
|
86
|
+
if Utils.ar_version >= 7.1
|
87
|
+
ActiveSupport::Deprecation.new(nil, "online_migrations")
|
88
|
+
else
|
89
|
+
ActiveSupport::Deprecation
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
83
93
|
# @private
|
84
94
|
def load
|
85
95
|
require "active_record/connection_adapters/postgresql_adapter"
|
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.12.0
|
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-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -104,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
104
|
- !ruby/object:Gem::Version
|
105
105
|
version: '0'
|
106
106
|
requirements: []
|
107
|
-
rubygems_version: 3.4
|
107
|
+
rubygems_version: 3.5.4
|
108
108
|
signing_key:
|
109
109
|
specification_version: 4
|
110
110
|
summary: Catch unsafe PostgreSQL migrations in development and run them easier in
|