online_migrations 0.26.0 → 0.27.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 +18 -0
- 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/lib/generators/online_migrations/{background_migration_generator.rb → data_migration_generator.rb} +4 -4
- 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 +19 -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 +160 -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 +51 -123
- data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +25 -46
- 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/change_column_type_helpers.rb +17 -4
- data/lib/online_migrations/config.rb +4 -4
- data/lib/online_migrations/data_migration.rb +127 -0
- data/lib/online_migrations/error_messages.rb +2 -0
- data/lib/online_migrations/lock_retrier.rb +5 -2
- data/lib/online_migrations/schema_statements.rb +1 -1
- data/lib/online_migrations/shard_aware.rb +44 -0
- data/lib/online_migrations/version.rb +1 -1
- data/lib/online_migrations.rb +18 -11
- metadata +22 -21
- 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
|
@@ -214,28 +155,11 @@ module OnlineMigrations
|
|
214
155
|
end
|
215
156
|
|
216
157
|
private
|
217
|
-
def validate_children_statuses
|
218
|
-
if composite?
|
219
|
-
if succeeded? && children.except_succeeded.exists?
|
220
|
-
errors.add(:base, "all child migrations must be succeeded")
|
221
|
-
elsif failed? && !children.failed.exists?
|
222
|
-
errors.add(:base, "at least one child migration must be failed")
|
223
|
-
end
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
def validate_connection_class
|
228
|
-
klass = connection_class_name.safe_constantize
|
229
|
-
if !(klass <= ActiveRecord::Base)
|
230
|
-
errors.add(:connection_class_name, "is not an ActiveRecord::Base child class")
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
158
|
def validate_table_exists
|
235
159
|
# Skip this validation if we have invalid connection class name.
|
236
160
|
return if errors.include?(:connection_class_name)
|
237
161
|
|
238
|
-
|
162
|
+
on_shard_if_present do
|
239
163
|
if !connection_class.connection.table_exists?(table_name)
|
240
164
|
errors.add(:table_name, "'#{table_name}' does not exist")
|
241
165
|
end
|
@@ -248,9 +172,13 @@ module OnlineMigrations
|
|
248
172
|
self.statement_timeout ||= config.statement_timeout
|
249
173
|
end
|
250
174
|
|
251
|
-
def
|
252
|
-
|
253
|
-
|
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)
|
254
182
|
end
|
255
183
|
|
256
184
|
def with_statement_timeout(connection, timeout)
|
@@ -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 }
|
@@ -152,7 +152,11 @@ module OnlineMigrations
|
|
152
152
|
# revert_initialize_column_type_change(:files, :size)
|
153
153
|
#
|
154
154
|
def revert_initialize_column_type_change(table_name, column_name, _new_type = nil, **_options)
|
155
|
-
|
155
|
+
tmp_column_name = __change_type_column(column_name)
|
156
|
+
transaction do
|
157
|
+
__remove_copy_triggers(table_name, column_name, tmp_column_name)
|
158
|
+
remove_column(table_name, tmp_column_name)
|
159
|
+
end
|
156
160
|
end
|
157
161
|
|
158
162
|
# Same as `revert_initialize_column_type_change` but for multiple columns.
|
@@ -160,7 +164,12 @@ module OnlineMigrations
|
|
160
164
|
#
|
161
165
|
def revert_initialize_columns_type_change(table_name, columns_and_types, **_options)
|
162
166
|
column_names = columns_and_types.map(&:first)
|
163
|
-
|
167
|
+
tmp_column_names = column_names.map { |column_name| __change_type_column(column_name) }
|
168
|
+
|
169
|
+
transaction do
|
170
|
+
__remove_copy_triggers(table_name, column_names, tmp_column_names)
|
171
|
+
remove_columns(table_name, *tmp_column_names)
|
172
|
+
end
|
164
173
|
end
|
165
174
|
|
166
175
|
# Backfills data from the old column to the new column.
|
@@ -347,6 +356,7 @@ module OnlineMigrations
|
|
347
356
|
# the original column type to be able to revert.
|
348
357
|
#
|
349
358
|
def cleanup_column_type_change(table_name, column_name)
|
359
|
+
__ensure_not_in_transaction!
|
350
360
|
cleanup_columns_type_change(table_name, column_name)
|
351
361
|
end
|
352
362
|
|
@@ -354,6 +364,8 @@ module OnlineMigrations
|
|
354
364
|
# @see #cleanup_column_type_change
|
355
365
|
#
|
356
366
|
def cleanup_columns_type_change(table_name, *column_names)
|
367
|
+
__ensure_not_in_transaction!
|
368
|
+
|
357
369
|
tmp_column_names = column_names.map { |column_name| __change_type_column(column_name) }
|
358
370
|
|
359
371
|
# Safely remove existing indexes and foreign keys first, if any.
|
@@ -477,10 +489,11 @@ module OnlineMigrations
|
|
477
489
|
end
|
478
490
|
|
479
491
|
def __copy_check_constraints(table_name, from_column, to_column)
|
480
|
-
|
492
|
+
from_column_re = /\b#{from_column}\b/
|
493
|
+
check_constraints = check_constraints(table_name).select { |c| c.expression.match?(from_column_re) }
|
481
494
|
|
482
495
|
check_constraints.each do |check|
|
483
|
-
new_expression = check.expression.gsub(
|
496
|
+
new_expression = check.expression.gsub(from_column_re, to_column)
|
484
497
|
|
485
498
|
add_check_constraint(table_name, new_expression, validate: false)
|
486
499
|
|
@@ -210,10 +210,10 @@ module OnlineMigrations
|
|
210
210
|
|
211
211
|
# Configuration object to configure background migrations
|
212
212
|
#
|
213
|
-
# @return [
|
214
|
-
# @see
|
213
|
+
# @return [BackgroundDataMigrations::Config]
|
214
|
+
# @see BackgroundDataMigrations::Config
|
215
215
|
#
|
216
|
-
attr_reader :
|
216
|
+
attr_reader :background_data_migrations
|
217
217
|
|
218
218
|
attr_reader :background_schema_migrations
|
219
219
|
|
@@ -230,7 +230,7 @@ module OnlineMigrations
|
|
230
230
|
lock_timeout: 0.2.seconds
|
231
231
|
)
|
232
232
|
|
233
|
-
@
|
233
|
+
@background_data_migrations = BackgroundDataMigrations::Config.new
|
234
234
|
@background_schema_migrations = BackgroundSchemaMigrations::Config.new
|
235
235
|
|
236
236
|
@checks = []
|