online_migrations 0.25.0 → 0.27.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 +31 -0
- data/README.md +18 -73
- data/docs/0.27-upgrade.md +24 -0
- data/docs/background_data_migrations.md +200 -101
- data/docs/background_schema_migrations.md +2 -2
- data/docs/configuring.md +8 -0
- data/lib/generators/online_migrations/{background_migration_generator.rb → data_migration_generator.rb} +4 -4
- data/lib/generators/online_migrations/templates/add_sharding_to_online_migrations.rb.tt +1 -1
- data/lib/generators/online_migrations/templates/add_timestamps_to_background_migrations.rb.tt +1 -1
- data/lib/generators/online_migrations/templates/background_schema_migrations_change_unique_index.rb.tt +1 -1
- data/lib/generators/online_migrations/templates/change_background_data_migrations.rb.tt +34 -0
- data/lib/generators/online_migrations/templates/{background_data_migration.rb.tt → data_migration.rb.tt} +8 -9
- data/lib/generators/online_migrations/templates/initializer.rb.tt +22 -25
- data/lib/generators/online_migrations/templates/install_migration.rb.tt +9 -40
- data/lib/generators/online_migrations/upgrade_generator.rb +16 -8
- data/lib/online_migrations/active_record_batch_enumerator.rb +8 -0
- data/lib/online_migrations/background_data_migrations/backfill_column.rb +50 -0
- data/lib/online_migrations/background_data_migrations/config.rb +62 -0
- data/lib/online_migrations/{background_migrations → background_data_migrations}/copy_column.rb +15 -28
- data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_associated_records.rb +9 -5
- data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_orphaned_records.rb +5 -9
- data/lib/online_migrations/background_data_migrations/migration.rb +312 -0
- data/lib/online_migrations/{background_migrations → background_data_migrations}/migration_helpers.rb +72 -61
- data/lib/online_migrations/background_data_migrations/migration_job.rb +158 -0
- data/lib/online_migrations/background_data_migrations/migration_status_validator.rb +65 -0
- data/lib/online_migrations/{background_migrations → background_data_migrations}/perform_action_on_relation.rb +5 -5
- data/lib/online_migrations/{background_migrations → background_data_migrations}/reset_counters.rb +5 -5
- data/lib/online_migrations/background_data_migrations/scheduler.rb +78 -0
- data/lib/online_migrations/background_data_migrations/ticker.rb +62 -0
- data/lib/online_migrations/background_schema_migrations/config.rb +2 -2
- data/lib/online_migrations/background_schema_migrations/migration.rb +57 -127
- data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +26 -47
- data/lib/online_migrations/background_schema_migrations/migration_runner.rb +43 -97
- data/lib/online_migrations/background_schema_migrations/scheduler.rb +2 -2
- data/lib/online_migrations/batch_iterator.rb +7 -4
- data/lib/online_migrations/change_column_type_helpers.rb +75 -14
- data/lib/online_migrations/command_checker.rb +32 -20
- data/lib/online_migrations/config.rb +12 -4
- data/lib/online_migrations/data_migration.rb +127 -0
- data/lib/online_migrations/error_messages.rb +16 -0
- data/lib/online_migrations/index_definition.rb +1 -1
- data/lib/online_migrations/lock_retrier.rb +5 -2
- data/lib/online_migrations/migration.rb +8 -1
- data/lib/online_migrations/schema_cache.rb +0 -78
- data/lib/online_migrations/schema_statements.rb +18 -74
- data/lib/online_migrations/shard_aware.rb +44 -0
- data/lib/online_migrations/utils.rb +1 -20
- data/lib/online_migrations/verbose_sql_logs.rb +1 -7
- data/lib/online_migrations/version.rb +1 -1
- data/lib/online_migrations.rb +19 -19
- metadata +25 -24
- data/lib/online_migrations/background_migration.rb +0 -64
- data/lib/online_migrations/background_migrations/backfill_column.rb +0 -54
- data/lib/online_migrations/background_migrations/background_migration_class_validator.rb +0 -29
- data/lib/online_migrations/background_migrations/config.rb +0 -74
- data/lib/online_migrations/background_migrations/migration.rb +0 -329
- data/lib/online_migrations/background_migrations/migration_job.rb +0 -109
- data/lib/online_migrations/background_migrations/migration_job_runner.rb +0 -66
- data/lib/online_migrations/background_migrations/migration_job_status_validator.rb +0 -29
- data/lib/online_migrations/background_migrations/migration_runner.rb +0 -161
- data/lib/online_migrations/background_migrations/migration_status_validator.rb +0 -48
- data/lib/online_migrations/background_migrations/scheduler.rb +0 -42
@@ -8,13 +8,15 @@ module OnlineMigrations
|
|
8
8
|
# `enqueue_background_schema_migration` helper inside migrations.
|
9
9
|
#
|
10
10
|
class Migration < ApplicationRecord
|
11
|
+
include ShardAware
|
12
|
+
|
11
13
|
STATUSES = [
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
"enqueued", # The migration has been enqueued by the user.
|
15
|
+
"running", # The migration is being performed by a migration executor.
|
16
|
+
"errored", # The migration raised an error during last run.
|
17
|
+
"failed", # The migration raises an error when running and retry attempts exceeded.
|
18
|
+
"succeeded", # The migration finished without error.
|
19
|
+
"cancelled", # The migration was cancelled by the user.
|
18
20
|
]
|
19
21
|
|
20
22
|
MAX_IDENTIFIER_LENGTH = 63
|
@@ -22,36 +24,12 @@ module OnlineMigrations
|
|
22
24
|
self.table_name = :background_schema_migrations
|
23
25
|
|
24
26
|
scope :queue_order, -> { order(created_at: :asc) }
|
25
|
-
scope :parents, -> { where(parent_id: nil) }
|
26
|
-
scope :runnable, -> { where(composite: false) }
|
27
27
|
scope :active, -> { where(status: [:enqueued, :running, :errored]) }
|
28
|
-
scope :except_succeeded, -> { where.not(status: :succeeded) }
|
29
|
-
|
30
|
-
scope :stuck, -> do
|
31
|
-
runnable.active.where(<<~SQL)
|
32
|
-
updated_at <= NOW() - interval '1 second' * (COALESCE(statement_timeout, 60*60*24) + 60*10)
|
33
|
-
SQL
|
34
|
-
end
|
35
|
-
|
36
|
-
scope :retriable, -> do
|
37
|
-
stuck_sql = connection.unprepared_statement { stuck.to_sql }
|
38
|
-
|
39
|
-
from(Arel.sql(<<~SQL))
|
40
|
-
(
|
41
|
-
(SELECT * FROM background_schema_migrations WHERE NOT composite AND status = 'errored')
|
42
|
-
UNION
|
43
|
-
(#{stuck_sql})
|
44
|
-
) AS #{table_name}
|
45
|
-
SQL
|
46
|
-
end
|
47
28
|
|
48
29
|
alias_attribute :name, :migration_name
|
49
30
|
|
50
31
|
enum :status, STATUSES.index_with(&:to_s)
|
51
32
|
|
52
|
-
belongs_to :parent, class_name: name, optional: true, inverse_of: :children
|
53
|
-
has_many :children, class_name: name, foreign_key: :parent_id, inverse_of: :parent
|
54
|
-
|
55
33
|
validates :table_name, presence: true, length: { maximum: MAX_IDENTIFIER_LENGTH }
|
56
34
|
validates :definition, presence: true
|
57
35
|
validates :migration_name, presence: true, uniqueness: {
|
@@ -65,70 +43,53 @@ module OnlineMigrations
|
|
65
43
|
end,
|
66
44
|
}
|
67
45
|
|
68
|
-
validate :validate_children_statuses, if: -> { composite? && status_changed? }
|
69
|
-
validate :validate_connection_class, if: :connection_class_name?
|
70
46
|
validate :validate_table_exists
|
71
47
|
validates_with MigrationStatusValidator, on: :update
|
72
48
|
|
73
49
|
before_validation :set_defaults
|
74
50
|
|
51
|
+
# Returns whether the migration is completed, which is defined as
|
52
|
+
# having a status of succeeded, failed, or cancelled.
|
53
|
+
#
|
54
|
+
# @return [Boolean] whether the migration is completed.
|
55
|
+
#
|
75
56
|
def completed?
|
76
|
-
succeeded? || failed?
|
57
|
+
succeeded? || failed? || cancelled?
|
77
58
|
end
|
78
59
|
|
79
|
-
#
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
end
|
60
|
+
# Returns whether the migration is active, which is defined as
|
61
|
+
# having a status of enqueued, or running.
|
62
|
+
#
|
63
|
+
# @return [Boolean] whether the migration is active.
|
64
|
+
#
|
65
|
+
def active?
|
66
|
+
enqueued? || running?
|
87
67
|
end
|
68
|
+
|
88
69
|
alias cancel cancelled!
|
89
70
|
|
71
|
+
# Returns whether this migration is pausable.
|
72
|
+
#
|
90
73
|
def pausable?
|
91
74
|
false
|
92
75
|
end
|
93
76
|
|
94
|
-
|
95
|
-
false
|
96
|
-
end
|
97
|
-
|
98
|
-
def can_be_cancelled?
|
99
|
-
!succeeded? && !cancelled?
|
100
|
-
end
|
101
|
-
|
102
|
-
# Returns the progress of the background schema migration.
|
77
|
+
# Dummy method to support the same interface as background data migrations.
|
103
78
|
#
|
104
|
-
# @return [
|
79
|
+
# @return [nil]
|
105
80
|
#
|
106
81
|
def progress
|
107
|
-
if succeeded?
|
108
|
-
100.0
|
109
|
-
elsif composite?
|
110
|
-
progresses = children.map(&:progress)
|
111
|
-
# There should not be composite migrations without children,
|
112
|
-
# but children may be deleted for some reason, so we need to
|
113
|
-
# make a check to avoid 0 division error.
|
114
|
-
if progresses.any?
|
115
|
-
(progresses.sum.to_f / progresses.size).round(2)
|
116
|
-
else
|
117
|
-
0.0
|
118
|
-
end
|
119
|
-
else
|
120
|
-
0.0
|
121
|
-
end
|
122
82
|
end
|
123
83
|
|
124
84
|
# Whether the migration is considered stuck (is running for some configured time).
|
125
85
|
#
|
126
86
|
def stuck?
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
87
|
+
if index_addition?
|
88
|
+
running? && !index_build_in_progress?
|
89
|
+
else
|
90
|
+
stuck_timeout = (statement_timeout || 1.day) + 10.minutes
|
91
|
+
running? && updated_at <= stuck_timeout.seconds.ago
|
92
|
+
end
|
132
93
|
end
|
133
94
|
|
134
95
|
# Mark this migration as ready to be processed again.
|
@@ -136,27 +97,16 @@ module OnlineMigrations
|
|
136
97
|
# This is used to manually retrying failed migrations.
|
137
98
|
#
|
138
99
|
def retry
|
139
|
-
if
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
update!(
|
151
|
-
status: :enqueued,
|
152
|
-
attempts: 0,
|
153
|
-
started_at: nil,
|
154
|
-
finished_at: nil,
|
155
|
-
error_class: nil,
|
156
|
-
error_message: nil,
|
157
|
-
backtrace: nil
|
158
|
-
)
|
159
|
-
end
|
100
|
+
if failed?
|
101
|
+
update!(
|
102
|
+
status: :enqueued,
|
103
|
+
attempts: 0,
|
104
|
+
started_at: nil,
|
105
|
+
finished_at: nil,
|
106
|
+
error_class: nil,
|
107
|
+
error_message: nil,
|
108
|
+
backtrace: nil
|
109
|
+
)
|
160
110
|
|
161
111
|
true
|
162
112
|
else
|
@@ -168,15 +118,6 @@ module OnlineMigrations
|
|
168
118
|
definition.match?(/create (unique )?index/i)
|
169
119
|
end
|
170
120
|
|
171
|
-
# @private
|
172
|
-
def connection_class
|
173
|
-
if connection_class_name && (klass = connection_class_name.safe_constantize)
|
174
|
-
Utils.find_connection_class(klass)
|
175
|
-
else
|
176
|
-
ActiveRecord::Base
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
121
|
# @private
|
181
122
|
def attempts_exceeded?
|
182
123
|
attempts >= max_attempts
|
@@ -184,7 +125,7 @@ module OnlineMigrations
|
|
184
125
|
|
185
126
|
# @private
|
186
127
|
def run
|
187
|
-
|
128
|
+
on_shard_if_present do
|
188
129
|
connection = connection_class.connection
|
189
130
|
|
190
131
|
connection.with_lock_retries do
|
@@ -194,10 +135,7 @@ module OnlineMigrations
|
|
194
135
|
if index_addition?
|
195
136
|
index = connection.indexes(table_name).find { |i| i.name == name }
|
196
137
|
if index
|
197
|
-
|
198
|
-
# when switching to ActiveRecord >= 7.1.
|
199
|
-
schema = connection.send(:__schema_for_table, table_name)
|
200
|
-
if connection.send(:__index_valid?, name, schema: schema)
|
138
|
+
if index.valid?
|
201
139
|
return
|
202
140
|
else
|
203
141
|
connection.remove_index(table_name, name: name, algorithm: :concurrently)
|
@@ -206,34 +144,22 @@ module OnlineMigrations
|
|
206
144
|
end
|
207
145
|
|
208
146
|
connection.execute(definition)
|
147
|
+
|
148
|
+
# Outdated statistics + a new index can hurt performance of existing queries.
|
149
|
+
if OnlineMigrations.config.auto_analyze
|
150
|
+
connection.execute("ANALYZE #{table_name}")
|
151
|
+
end
|
209
152
|
end
|
210
153
|
end
|
211
154
|
end
|
212
155
|
end
|
213
156
|
|
214
157
|
private
|
215
|
-
def validate_children_statuses
|
216
|
-
if composite?
|
217
|
-
if succeeded? && children.except_succeeded.exists?
|
218
|
-
errors.add(:base, "all child migrations must be succeeded")
|
219
|
-
elsif failed? && !children.failed.exists?
|
220
|
-
errors.add(:base, "at least one child migration must be failed")
|
221
|
-
end
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
def validate_connection_class
|
226
|
-
klass = connection_class_name.safe_constantize
|
227
|
-
if !(klass <= ActiveRecord::Base)
|
228
|
-
errors.add(:connection_class_name, "is not an ActiveRecord::Base child class")
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
158
|
def validate_table_exists
|
233
159
|
# Skip this validation if we have invalid connection class name.
|
234
160
|
return if errors.include?(:connection_class_name)
|
235
161
|
|
236
|
-
|
162
|
+
on_shard_if_present do
|
237
163
|
if !connection_class.connection.table_exists?(table_name)
|
238
164
|
errors.add(:table_name, "'#{table_name}' does not exist")
|
239
165
|
end
|
@@ -246,9 +172,13 @@ module OnlineMigrations
|
|
246
172
|
self.statement_timeout ||= config.statement_timeout
|
247
173
|
end
|
248
174
|
|
249
|
-
def
|
250
|
-
|
251
|
-
|
175
|
+
def index_build_in_progress?
|
176
|
+
indexes_in_progress = connection_class.connection.select_values(<<~SQL)
|
177
|
+
SELECT index_relid::regclass::text
|
178
|
+
FROM pg_stat_progress_create_index
|
179
|
+
SQL
|
180
|
+
|
181
|
+
indexes_in_progress.include?(name)
|
252
182
|
end
|
253
183
|
|
254
184
|
def with_statement_timeout(connection, timeout)
|
@@ -19,7 +19,7 @@ module OnlineMigrations
|
|
19
19
|
return
|
20
20
|
end
|
21
21
|
|
22
|
-
if index_exists?(table_name, column_name, **options)
|
22
|
+
if index_exists?(table_name, column_name, name: index.name, **options)
|
23
23
|
Utils.raise_or_say("Index creation was not enqueued because the index already exists.")
|
24
24
|
return
|
25
25
|
end
|
@@ -78,67 +78,46 @@ module OnlineMigrations
|
|
78
78
|
# ensure_background_schema_migration_succeeded("index_users_on_email")
|
79
79
|
#
|
80
80
|
def ensure_background_schema_migration_succeeded(migration_name)
|
81
|
-
|
81
|
+
migrations = Migration.where(migration_name: migration_name).to_a
|
82
82
|
|
83
|
-
if
|
84
|
-
Utils.raise_in_prod_or_say_in_dev("Could not find background schema migration: '#{migration_name}'")
|
85
|
-
elsif !
|
86
|
-
raise "Expected background schema migration '#{migration_name}' to be marked as 'succeeded'
|
87
|
-
"but it is '#{migration.status}'."
|
83
|
+
if migrations.empty?
|
84
|
+
Utils.raise_in_prod_or_say_in_dev("Could not find background schema migration(s): '#{migration_name}'.")
|
85
|
+
elsif !migrations.all?(&:succeeded?)
|
86
|
+
raise "Expected background schema migration(s) '#{migration_name}' to be marked as 'succeeded'."
|
88
87
|
end
|
89
88
|
end
|
90
89
|
|
91
|
-
def enqueue_background_schema_migration(
|
92
|
-
|
93
|
-
raise ArgumentError, "You must pass a :connection_class_name when using multiple databases."
|
94
|
-
end
|
95
|
-
|
96
|
-
migration = create_background_schema_migration(name, table_name, **options)
|
90
|
+
def enqueue_background_schema_migration(migration_name, table_name, connection_class_name: nil, **options)
|
91
|
+
options.assert_valid_keys(:definition, :max_attempts, :statement_timeout)
|
97
92
|
|
98
|
-
|
99
|
-
|
100
|
-
runner = MigrationRunner.new(migration)
|
101
|
-
runner.run
|
93
|
+
if Utils.multiple_databases? && !connection_class_name
|
94
|
+
raise ArgumentError, "You must pass a :connection_class_name when using multiple databases."
|
102
95
|
end
|
103
96
|
|
104
|
-
migration
|
105
|
-
end
|
106
|
-
|
107
|
-
# @private
|
108
|
-
def create_background_schema_migration(migration_name, table_name, connection_class_name: nil, **options)
|
109
|
-
options.assert_valid_keys(:definition, :max_attempts, :statement_timeout)
|
110
|
-
|
111
97
|
if connection_class_name
|
112
|
-
|
98
|
+
klass = connection_class_name.constantize
|
99
|
+
connection_class = Utils.find_connection_class(klass)
|
100
|
+
# Normalize to the real connection class name.
|
101
|
+
connection_class_name = connection_class.name
|
102
|
+
else
|
103
|
+
connection_class = ActiveRecord::Base
|
113
104
|
end
|
114
105
|
|
115
|
-
|
116
|
-
|
117
|
-
migration.assign_attributes(**options, table_name: table_name)
|
106
|
+
shards = Utils.shard_names(connection_class)
|
107
|
+
shards = [nil] if shards.size == 1
|
118
108
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
child = migration.dup
|
123
|
-
child.shard = shard
|
124
|
-
child
|
125
|
-
end
|
109
|
+
shards.each do |shard|
|
110
|
+
migration = Migration.create_with(**options, table_name: table_name)
|
111
|
+
.find_or_create_by!(migration_name: migration_name, shard: shard, connection_class_name: connection_class_name)
|
126
112
|
|
127
|
-
|
113
|
+
if Utils.run_background_migrations_inline?
|
114
|
+
runner = MigrationRunner.new(migration)
|
115
|
+
runner.run
|
128
116
|
end
|
129
117
|
end
|
130
|
-
end
|
131
118
|
|
132
|
-
|
133
|
-
|
134
|
-
if connection_class_name
|
135
|
-
klass = connection_class_name.safe_constantize
|
136
|
-
if klass
|
137
|
-
connection_class = Utils.find_connection_class(klass)
|
138
|
-
connection_class.name if connection_class
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
119
|
+
true
|
120
|
+
end
|
142
121
|
end
|
143
122
|
end
|
144
123
|
end
|
@@ -13,119 +13,65 @@ module OnlineMigrations
|
|
13
13
|
def run
|
14
14
|
return if migration.cancelled? || migration.succeeded?
|
15
15
|
|
16
|
-
|
16
|
+
migration.running! if migration.enqueued? || migration.errored?
|
17
|
+
migration_payload = { migration: migration }
|
17
18
|
|
18
|
-
if migration.
|
19
|
-
|
20
|
-
runner = self.class.new(child_migration)
|
21
|
-
runner.run
|
22
|
-
end
|
19
|
+
if migration.attempts == 0
|
20
|
+
ActiveSupport::Notifications.instrument("started.background_schema_migrations", migration_payload)
|
23
21
|
else
|
24
|
-
|
22
|
+
ActiveSupport::Notifications.instrument("retried.background_schema_migrations", migration_payload)
|
25
23
|
end
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
def mark_as_running
|
30
|
-
Migration.transaction do
|
31
|
-
migration.running!
|
32
24
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
else
|
37
|
-
parent.update!(status: :running, started_at: Time.current, finished_at: nil)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
25
|
+
if should_throttle?
|
26
|
+
ActiveSupport::Notifications.instrument("throttled.background_schema_migrations", migration_payload)
|
27
|
+
return
|
41
28
|
end
|
42
29
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
migration.update!(
|
58
|
-
attempts: migration.attempts + 1,
|
59
|
-
status: :running,
|
60
|
-
started_at: Time.current,
|
61
|
-
finished_at: nil,
|
62
|
-
error_class: nil,
|
63
|
-
error_message: nil,
|
64
|
-
backtrace: nil
|
65
|
-
)
|
66
|
-
|
67
|
-
ActiveSupport::Notifications.instrument("run.background_schema_migrations", migration_payload) do
|
68
|
-
migration.run
|
69
|
-
end
|
70
|
-
|
71
|
-
# Background schema migrations could take a while to run. It is possible, that the process
|
72
|
-
# never reaches this (or the rescue below) line of code. E.g., when it is force quitted
|
73
|
-
# (SIGKILL etc.) and so the migration will end up in the "running" state and the query is
|
74
|
-
# still executing (or already finished) in the database. This migration can either be safely
|
75
|
-
# manually retried or will be picked up in the future by scheduler when it decides that
|
76
|
-
# this migration is stuck.
|
77
|
-
|
78
|
-
migration.update!(status: :succeeded, finished_at: Time.current)
|
30
|
+
migration.update!(
|
31
|
+
attempts: migration.attempts + 1,
|
32
|
+
status: :running,
|
33
|
+
started_at: Time.current,
|
34
|
+
finished_at: nil,
|
35
|
+
error_class: nil,
|
36
|
+
error_message: nil,
|
37
|
+
backtrace: nil
|
38
|
+
)
|
39
|
+
|
40
|
+
ActiveSupport::Notifications.instrument("run.background_schema_migrations", migration_payload) do
|
41
|
+
migration.run
|
42
|
+
end
|
79
43
|
|
80
|
-
|
44
|
+
# Background schema migrations could take a while to run. It is possible, that the process
|
45
|
+
# never reaches this (or the rescue below) line of code. E.g., when it is force quitted
|
46
|
+
# (SIGKILL etc.) and so the migration will end up in the "running" state and the query is
|
47
|
+
# still executing (or already finished) in the database. This migration can either be safely
|
48
|
+
# manually retried or will be picked up in the future by scheduler when it decides that
|
49
|
+
# this migration is stuck.
|
81
50
|
|
82
|
-
|
83
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
84
|
-
backtrace_cleaner = ::OnlineMigrations.config.backtrace_cleaner
|
51
|
+
migration.update!(status: :succeeded, finished_at: Time.current)
|
85
52
|
|
86
|
-
|
53
|
+
ActiveSupport::Notifications.instrument("completed.background_schema_migrations", migration_payload)
|
54
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
55
|
+
backtrace_cleaner = ::OnlineMigrations.config.backtrace_cleaner
|
87
56
|
|
88
|
-
|
89
|
-
status: status,
|
90
|
-
finished_at: Time.current,
|
91
|
-
error_class: e.class.name,
|
92
|
-
error_message: e.message,
|
93
|
-
backtrace: backtrace_cleaner ? backtrace_cleaner.clean(e.backtrace) : e.backtrace
|
94
|
-
)
|
57
|
+
status = migration.attempts_exceeded? ? :failed : :errored
|
95
58
|
|
96
|
-
|
59
|
+
migration.update!(
|
60
|
+
status: status,
|
61
|
+
finished_at: Time.current,
|
62
|
+
error_class: e.class.name,
|
63
|
+
error_message: e.message,
|
64
|
+
backtrace: backtrace_cleaner ? backtrace_cleaner.clean(e.backtrace) : e.backtrace
|
65
|
+
)
|
97
66
|
|
98
|
-
|
99
|
-
|
100
|
-
|
67
|
+
::OnlineMigrations.config.background_schema_migrations.error_handler.call(e, migration)
|
68
|
+
raise if Utils.run_background_migrations_inline?
|
69
|
+
end
|
101
70
|
|
71
|
+
private
|
102
72
|
def should_throttle?
|
103
73
|
::OnlineMigrations.config.throttler.call
|
104
74
|
end
|
105
|
-
|
106
|
-
def complete_parent_if_needed(migration)
|
107
|
-
parent = migration.parent
|
108
|
-
completed = false
|
109
|
-
|
110
|
-
parent.with_lock do
|
111
|
-
children = parent.children.select(:status)
|
112
|
-
if children.all?(&:succeeded?)
|
113
|
-
parent.update!(status: :succeeded, finished_at: Time.current)
|
114
|
-
completed = true
|
115
|
-
elsif children.any?(&:failed?)
|
116
|
-
parent.update!(status: :failed, finished_at: Time.current)
|
117
|
-
completed = true
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
if completed
|
122
|
-
ActiveSupport::Notifications.instrument("completed.background_migrations", notifications_payload(migration))
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def notifications_payload(migration)
|
127
|
-
{ background_schema_migration: migration }
|
128
|
-
end
|
129
75
|
end
|
130
76
|
end
|
131
77
|
end
|
@@ -35,8 +35,8 @@ module OnlineMigrations
|
|
35
35
|
|
36
36
|
private
|
37
37
|
def find_migration(**options)
|
38
|
-
active_migrations = Migration.running.
|
39
|
-
runnable_migrations = Migration.
|
38
|
+
stuck_migrations, active_migrations = Migration.running.partition(&:stuck?)
|
39
|
+
runnable_migrations = (Migration.enqueued + Migration.errored + stuck_migrations).sort_by(&:created_at)
|
40
40
|
|
41
41
|
if options.key?(:shard)
|
42
42
|
runnable_migrations = runnable_migrations.select { |migration| migration.shard.to_s == options[:shard].to_s }
|
@@ -19,8 +19,7 @@ module OnlineMigrations
|
|
19
19
|
end
|
20
20
|
|
21
21
|
relation = apply_limits(self.relation, column, start, finish, order)
|
22
|
-
|
23
|
-
base_relation = relation.unscope(*unscopes).reselect(column).reorder(column => order)
|
22
|
+
base_relation = relation.unscope(:includes, :preload, :eager_load).reselect(column).reorder(column => order)
|
24
23
|
|
25
24
|
start_id = start || begin
|
26
25
|
start_row = base_relation.uncached { base_relation.first }
|
@@ -84,12 +83,16 @@ module OnlineMigrations
|
|
84
83
|
|
85
84
|
private
|
86
85
|
def apply_limits(relation, column, start, finish, order)
|
86
|
+
arel_column = relation.arel_table[column]
|
87
|
+
|
87
88
|
if start
|
88
|
-
|
89
|
+
predicate = order == :asc ? :gteq : :lteq
|
90
|
+
relation = relation.where(arel_column.public_send(predicate, start))
|
89
91
|
end
|
90
92
|
|
91
93
|
if finish
|
92
|
-
|
94
|
+
predicate = order == :asc ? :lteq : :gteq
|
95
|
+
relation = relation.where(arel_column.public_send(predicate, finish))
|
93
96
|
end
|
94
97
|
|
95
98
|
relation
|