online_migrations 0.15.0 → 0.16.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 +6 -0
- data/README.md +10 -6
- data/docs/{background_migrations.md → background_data_migrations.md} +4 -33
- data/docs/background_schema_migrations.md +163 -0
- data/docs/configuring.md +30 -1
- data/lib/generators/online_migrations/templates/create_background_schema_migrations.rb.tt +29 -0
- data/lib/generators/online_migrations/templates/initializer.rb.tt +24 -11
- data/lib/generators/online_migrations/templates/install_migration.rb.tt +24 -0
- data/lib/generators/online_migrations/upgrade_generator.rb +5 -0
- data/lib/online_migrations/application_record.rb +11 -0
- data/lib/online_migrations/background_migrations/config.rb +27 -24
- data/lib/online_migrations/background_migrations/migration.rb +1 -8
- data/lib/online_migrations/background_migrations/migration_job_runner.rb +1 -1
- data/lib/online_migrations/background_migrations/migration_runner.rb +1 -1
- data/lib/online_migrations/background_schema_migrations/config.rb +40 -0
- data/lib/online_migrations/background_schema_migrations/migration.rb +205 -0
- data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +76 -0
- data/lib/online_migrations/background_schema_migrations/migration_runner.rb +110 -0
- data/lib/online_migrations/background_schema_migrations/migration_status_validator.rb +33 -0
- data/lib/online_migrations/background_schema_migrations/scheduler.rb +30 -0
- data/lib/online_migrations/config.rb +32 -0
- data/lib/online_migrations/schema_statements.rb +1 -0
- data/lib/online_migrations/utils.rb +7 -0
- data/lib/online_migrations/version.rb +1 -1
- data/lib/online_migrations.rb +19 -2
- metadata +12 -4
- data/lib/online_migrations/background_migrations/application_record.rb +0 -13
@@ -0,0 +1,205 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OnlineMigrations
|
4
|
+
module BackgroundSchemaMigrations
|
5
|
+
# Class representing background schema migration.
|
6
|
+
#
|
7
|
+
# @note The records of this class should not be created manually, but via
|
8
|
+
# `enqueue_background_schema_migration` helper inside migrations.
|
9
|
+
#
|
10
|
+
class Migration < ApplicationRecord
|
11
|
+
STATUSES = [
|
12
|
+
:enqueued, # The migration has been enqueued by the user.
|
13
|
+
:running, # The migration is being performed by a migration executor.
|
14
|
+
:failed, # The migration raises an exception when running.
|
15
|
+
:succeeded, # The migration finished without error.
|
16
|
+
]
|
17
|
+
|
18
|
+
MAX_IDENTIFIER_LENGTH = 63
|
19
|
+
|
20
|
+
self.table_name = :background_schema_migrations
|
21
|
+
|
22
|
+
scope :queue_order, -> { order(created_at: :asc) }
|
23
|
+
scope :runnable, -> { where(composite: false) }
|
24
|
+
scope :active, -> { where(status: [statuses[:enqueued], statuses[:running]]) }
|
25
|
+
scope :except_succeeded, -> { where.not(status: :succeeded) }
|
26
|
+
|
27
|
+
scope :stuck, -> do
|
28
|
+
runnable.active.where(<<~SQL)
|
29
|
+
updated_at <= NOW() - interval '1 second' * (COALESCE(statement_timeout, 60*60*24) + 60*10)
|
30
|
+
SQL
|
31
|
+
end
|
32
|
+
|
33
|
+
scope :retriable, -> do
|
34
|
+
failed_retriable = runnable.failed.where("attempts < max_attempts")
|
35
|
+
|
36
|
+
stuck_sql = connection.unprepared_statement { stuck.to_sql }
|
37
|
+
failed_retriable_sql = connection.unprepared_statement { failed_retriable.to_sql }
|
38
|
+
|
39
|
+
from(Arel.sql(<<~SQL))
|
40
|
+
(
|
41
|
+
(#{failed_retriable_sql})
|
42
|
+
UNION
|
43
|
+
(#{stuck_sql})
|
44
|
+
) AS #{table_name}
|
45
|
+
SQL
|
46
|
+
end
|
47
|
+
|
48
|
+
alias_attribute :name, :migration_name
|
49
|
+
|
50
|
+
# Avoid deprecation warnings.
|
51
|
+
if Utils.ar_version >= 7
|
52
|
+
enum :status, STATUSES.index_with(&:to_s)
|
53
|
+
else
|
54
|
+
enum status: STATUSES.index_with(&:to_s)
|
55
|
+
end
|
56
|
+
|
57
|
+
belongs_to :parent, class_name: name, optional: true
|
58
|
+
has_many :children, class_name: name, foreign_key: :parent_id
|
59
|
+
|
60
|
+
validates :migration_name, presence: true, uniqueness: { scope: :shard }
|
61
|
+
validates :table_name, presence: true, length: { maximum: MAX_IDENTIFIER_LENGTH }
|
62
|
+
validates :definition, presence: true
|
63
|
+
|
64
|
+
validate :validate_children_statuses, if: -> { composite? && status_changed? }
|
65
|
+
validate :validate_connection_class, if: :connection_class_name?
|
66
|
+
validate :validate_table_exists
|
67
|
+
validates_with MigrationStatusValidator, on: :update
|
68
|
+
|
69
|
+
before_validation :set_defaults
|
70
|
+
|
71
|
+
def completed?
|
72
|
+
succeeded? || failed?
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the progress of the background schema migration.
|
76
|
+
#
|
77
|
+
# @return [Float] value in range from 0.0 to 100.0
|
78
|
+
#
|
79
|
+
def progress
|
80
|
+
if succeeded?
|
81
|
+
100.0
|
82
|
+
elsif composite?
|
83
|
+
progresses = children.map(&:progress)
|
84
|
+
(progresses.sum.to_f / progresses.size).round(2)
|
85
|
+
else
|
86
|
+
0.0
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Mark this migration as ready to be processed again.
|
91
|
+
#
|
92
|
+
# This is used to manually retrying failed migrations.
|
93
|
+
#
|
94
|
+
def retry
|
95
|
+
if composite?
|
96
|
+
children.failed.each(&:retry)
|
97
|
+
elsif failed?
|
98
|
+
update!(
|
99
|
+
status: self.class.statuses[:enqueued],
|
100
|
+
attempts: 0,
|
101
|
+
started_at: nil,
|
102
|
+
finished_at: nil,
|
103
|
+
error_class: nil,
|
104
|
+
error_message: nil,
|
105
|
+
backtrace: nil
|
106
|
+
)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# @private
|
111
|
+
def connection_class
|
112
|
+
if connection_class_name && (klass = connection_class_name.safe_constantize)
|
113
|
+
Utils.find_connection_class(klass)
|
114
|
+
else
|
115
|
+
ActiveRecord::Base
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# @private
|
120
|
+
def attempts_exceeded?
|
121
|
+
attempts >= max_attempts
|
122
|
+
end
|
123
|
+
|
124
|
+
# @private
|
125
|
+
def run
|
126
|
+
on_shard do
|
127
|
+
connection = connection_class.connection
|
128
|
+
|
129
|
+
connection.with_lock_retries do
|
130
|
+
statement_timeout = self.statement_timeout || OnlineMigrations.config.statement_timeout
|
131
|
+
|
132
|
+
with_statement_timeout(connection, statement_timeout) do
|
133
|
+
case definition
|
134
|
+
when /create (unique )?index/i
|
135
|
+
index = connection.indexes(table_name).find { |i| i.name == name }
|
136
|
+
if index
|
137
|
+
# Use index validity from https://github.com/rails/rails/pull/45160
|
138
|
+
# when switching to ActiveRecord >= 7.1.
|
139
|
+
schema = connection.send(:__schema_for_table, table_name)
|
140
|
+
if connection.send(:__index_valid?, name, schema: schema)
|
141
|
+
return
|
142
|
+
else
|
143
|
+
connection.remove_index(table_name, name: name)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
connection.execute(definition)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
def validate_children_statuses
|
156
|
+
if composite?
|
157
|
+
if succeeded? && children.except_succeeded.exists?
|
158
|
+
errors.add(:base, "all child migrations must be succeeded")
|
159
|
+
elsif failed? && !children.failed.exists?
|
160
|
+
errors.add(:base, "at least one child migration must be failed")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def validate_connection_class
|
166
|
+
klass = connection_class_name.safe_constantize
|
167
|
+
if !(klass < ActiveRecord::Base)
|
168
|
+
errors.add(:connection_class_name, "is not an ActiveRecord::Base child class")
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def validate_table_exists
|
173
|
+
# Skip this validation if we have invalid connection class name.
|
174
|
+
return if errors.include?(:connection_class_name)
|
175
|
+
|
176
|
+
on_shard do
|
177
|
+
if !connection_class.connection.table_exists?(table_name)
|
178
|
+
errors.add(:table_name, "'#{table_name}' does not exist")
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def set_defaults
|
184
|
+
config = ::OnlineMigrations.config.background_schema_migrations
|
185
|
+
self.max_attempts ||= config.max_attempts
|
186
|
+
self.statement_timeout ||= config.statement_timeout
|
187
|
+
end
|
188
|
+
|
189
|
+
def on_shard(&block)
|
190
|
+
shard = (self.shard || connection_class.default_shard).to_sym
|
191
|
+
connection_class.connected_to(shard: shard, role: :writing, &block)
|
192
|
+
end
|
193
|
+
|
194
|
+
def with_statement_timeout(connection, timeout)
|
195
|
+
return yield if timeout.nil?
|
196
|
+
|
197
|
+
prev_value = connection.select_value("SHOW statement_timeout")
|
198
|
+
connection.execute("SET statement_timeout TO #{connection.quote(timeout.in_milliseconds)}")
|
199
|
+
yield
|
200
|
+
ensure
|
201
|
+
connection.execute("SET statement_timeout TO #{connection.quote(prev_value)}")
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OnlineMigrations
|
4
|
+
module BackgroundSchemaMigrations
|
5
|
+
module MigrationHelpers
|
6
|
+
def add_index_in_background(table_name, column_name, **options)
|
7
|
+
migration_options = options.extract!(:max_attempts, :statement_timeout, :connection_class_name)
|
8
|
+
|
9
|
+
if index_exists?(table_name, column_name, **options)
|
10
|
+
Utils.say("Index creation was not enqueued because the index already exists.")
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
options[:algorithm] = :concurrently
|
15
|
+
index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
|
16
|
+
|
17
|
+
create_index = ActiveRecord::ConnectionAdapters::CreateIndexDefinition.new(index, algorithm, if_not_exists)
|
18
|
+
schema_creation = ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaCreation.new(self)
|
19
|
+
definition = schema_creation.accept(create_index)
|
20
|
+
|
21
|
+
enqueue_background_schema_migration(index.name, table_name, definition: definition, **migration_options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def remove_index_in_background(table_name, column_name = nil, name:, **options)
|
25
|
+
raise ArgumentError, "Index name must be specified" if name.blank?
|
26
|
+
|
27
|
+
migration_options = options.extract!(:max_attempts, :statement_timeout, :connection_class_name)
|
28
|
+
|
29
|
+
if !index_exists?(table_name, column_name, **options, name: name)
|
30
|
+
Utils.say("Index deletion was not enqueued because the index does not exist.")
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
definition = "DROP INDEX CONCURRENTLY IF EXISTS #{quote_column_name(name)}"
|
35
|
+
enqueue_background_schema_migration(name, table_name, definition: definition, **migration_options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def enqueue_background_schema_migration(name, table_name, **options)
|
39
|
+
if options[:connection_class_name].nil? && Utils.multiple_databases?
|
40
|
+
raise ArgumentError, "You must pass a :connection_class_name when using multiple databases."
|
41
|
+
end
|
42
|
+
|
43
|
+
migration = create_background_schema_migration(name, table_name, **options)
|
44
|
+
|
45
|
+
run_inline = OnlineMigrations.config.run_background_migrations_inline
|
46
|
+
if run_inline && run_inline.call
|
47
|
+
runner = MigrationRunner.new(migration)
|
48
|
+
runner.run
|
49
|
+
end
|
50
|
+
|
51
|
+
migration
|
52
|
+
end
|
53
|
+
|
54
|
+
# @private
|
55
|
+
def create_background_schema_migration(migration_name, table_name, **options)
|
56
|
+
options.assert_valid_keys(:definition, :max_attempts, :statement_timeout, :connection_class_name)
|
57
|
+
migration = Migration.new(migration_name: migration_name, table_name: table_name, **options)
|
58
|
+
|
59
|
+
shards = Utils.shard_names(migration.connection_class)
|
60
|
+
if shards.size > 1
|
61
|
+
migration.children = shards.map do |shard|
|
62
|
+
child = migration.dup
|
63
|
+
child.shard = shard
|
64
|
+
child
|
65
|
+
end
|
66
|
+
|
67
|
+
migration.composite = true
|
68
|
+
end
|
69
|
+
|
70
|
+
# This will save all the records using a transaction.
|
71
|
+
migration.save!
|
72
|
+
migration
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OnlineMigrations
|
4
|
+
module BackgroundSchemaMigrations
|
5
|
+
# Runs single background schema migration.
|
6
|
+
class MigrationRunner
|
7
|
+
attr_reader :migration
|
8
|
+
|
9
|
+
def initialize(migration)
|
10
|
+
@migration = migration
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
mark_as_running if migration.enqueued? || migration.failed?
|
15
|
+
|
16
|
+
if migration.composite?
|
17
|
+
migration.children.each do |child_migration|
|
18
|
+
runner = self.class.new(child_migration)
|
19
|
+
runner.run
|
20
|
+
end
|
21
|
+
else
|
22
|
+
do_run
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def mark_as_running
|
28
|
+
Migration.transaction do
|
29
|
+
migration.running!
|
30
|
+
migration.parent.running! if migration.parent
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def do_run
|
35
|
+
migration_payload = notifications_payload(migration)
|
36
|
+
|
37
|
+
if migration.attempts == 0
|
38
|
+
ActiveSupport::Notifications.instrument("started.background_schema_migrations", migration_payload)
|
39
|
+
else
|
40
|
+
ActiveSupport::Notifications.instrument("retried.background_schema_migrations", migration_payload)
|
41
|
+
end
|
42
|
+
|
43
|
+
if should_throttle?
|
44
|
+
ActiveSupport::Notifications.instrument("throttled.background_schema_migrations", migration_payload)
|
45
|
+
return
|
46
|
+
end
|
47
|
+
|
48
|
+
migration.update!(
|
49
|
+
attempts: migration.attempts + 1,
|
50
|
+
status: :running,
|
51
|
+
started_at: Time.current,
|
52
|
+
finished_at: nil,
|
53
|
+
error_class: nil,
|
54
|
+
error_message: nil,
|
55
|
+
backtrace: nil
|
56
|
+
)
|
57
|
+
|
58
|
+
ActiveSupport::Notifications.instrument("run.background_schema_migrations", migration_payload) do
|
59
|
+
migration.run
|
60
|
+
end
|
61
|
+
|
62
|
+
migration.update!(status: :succeeded, finished_at: Time.current)
|
63
|
+
|
64
|
+
ActiveSupport::Notifications.instrument("completed.background_schema_migrations", migration_payload)
|
65
|
+
|
66
|
+
complete_parent_if_needed(migration) if migration.parent.present?
|
67
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
68
|
+
backtrace_cleaner = ::OnlineMigrations.config.backtrace_cleaner
|
69
|
+
|
70
|
+
migration.update!(
|
71
|
+
status: :failed,
|
72
|
+
finished_at: Time.current,
|
73
|
+
error_class: e.class.name,
|
74
|
+
error_message: e.message,
|
75
|
+
backtrace: backtrace_cleaner ? backtrace_cleaner.clean(e.backtrace) : e.backtrace
|
76
|
+
)
|
77
|
+
|
78
|
+
::OnlineMigrations.config.background_schema_migrations.error_handler.call(e, migration)
|
79
|
+
end
|
80
|
+
|
81
|
+
def should_throttle?
|
82
|
+
::OnlineMigrations.config.throttler.call
|
83
|
+
end
|
84
|
+
|
85
|
+
def complete_parent_if_needed(migration)
|
86
|
+
parent = migration.parent
|
87
|
+
completed = false
|
88
|
+
|
89
|
+
parent.with_lock do
|
90
|
+
children = parent.children.select(:status)
|
91
|
+
if children.all?(&:succeeded?)
|
92
|
+
parent.succeeded!
|
93
|
+
completed = true
|
94
|
+
elsif children.any?(&:failed?)
|
95
|
+
parent.failed!
|
96
|
+
completed = true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
if completed
|
101
|
+
ActiveSupport::Notifications.instrument("completed.background_migrations", notifications_payload(migration))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def notifications_payload(migration)
|
106
|
+
{ background_schema_migration: migration }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OnlineMigrations
|
4
|
+
module BackgroundSchemaMigrations
|
5
|
+
# @private
|
6
|
+
class MigrationStatusValidator < ActiveModel::Validator
|
7
|
+
VALID_STATUS_TRANSITIONS = {
|
8
|
+
# enqueued -> running occurs when the migration starts performing.
|
9
|
+
"enqueued" => ["running"],
|
10
|
+
# running -> succeeded occurs when the migration completes successfully.
|
11
|
+
# running -> failed occurs when the migration raises an exception when running and retry attempts exceeded.
|
12
|
+
"running" => ["succeeded", "failed"],
|
13
|
+
# failed -> enqueued occurs when the failed migration is enqueued to be retried.
|
14
|
+
# failed -> running occurs when the failed migration is retried.
|
15
|
+
"failed" => ["enqueued", "running"],
|
16
|
+
}
|
17
|
+
|
18
|
+
def validate(record)
|
19
|
+
return if !record.status_changed?
|
20
|
+
|
21
|
+
previous_status, new_status = record.status_change
|
22
|
+
valid_new_statuses = VALID_STATUS_TRANSITIONS.fetch(previous_status, [])
|
23
|
+
|
24
|
+
if !valid_new_statuses.include?(new_status)
|
25
|
+
record.errors.add(
|
26
|
+
:status,
|
27
|
+
"cannot transition background schema migration from status #{previous_status} to #{new_status}"
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OnlineMigrations
|
4
|
+
module BackgroundSchemaMigrations
|
5
|
+
# Class responsible for scheduling background schema migrations.
|
6
|
+
# It selects a single migration and runs it if there is no currently running migration.
|
7
|
+
#
|
8
|
+
# Scheduler should be configured to run periodically, for example, via cron.
|
9
|
+
# @example Run via whenever
|
10
|
+
# # add this to schedule.rb
|
11
|
+
# every 1.minute do
|
12
|
+
# runner "OnlineMigrations.run_background_schema_migrations"
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
class Scheduler
|
16
|
+
def self.run
|
17
|
+
new.run
|
18
|
+
end
|
19
|
+
|
20
|
+
# Runs Scheduler
|
21
|
+
def run
|
22
|
+
migration = Migration.runnable.enqueued.queue_order.first || Migration.retriable.queue_order.first
|
23
|
+
if migration
|
24
|
+
runner = MigrationRunner.new(migration)
|
25
|
+
runner.run
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -181,6 +181,26 @@ module OnlineMigrations
|
|
181
181
|
#
|
182
182
|
attr_accessor :run_background_migrations_inline
|
183
183
|
|
184
|
+
# Allows to throttle background data or schema migrations based on external signal (e.g. database health)
|
185
|
+
#
|
186
|
+
# It will be called before each run.
|
187
|
+
# If throttled, the current run will be retried next time.
|
188
|
+
#
|
189
|
+
# @return [Proc]
|
190
|
+
#
|
191
|
+
# @example
|
192
|
+
# OnlineMigrations.config.throttler = -> { DatabaseStatus.unhealthy? }
|
193
|
+
#
|
194
|
+
attr_reader :throttler
|
195
|
+
|
196
|
+
# The Active Support backtrace cleaner that will be used to clean the
|
197
|
+
# backtrace of a background data or schema migration that errors.
|
198
|
+
#
|
199
|
+
# @return [ActiveSupport::BacktraceCleaner, nil] the backtrace cleaner to
|
200
|
+
# use when cleaning a background migrations's backtrace. Defaults to `Rails.backtrace_cleaner`
|
201
|
+
#
|
202
|
+
attr_accessor :backtrace_cleaner
|
203
|
+
|
184
204
|
# Configuration object to configure background migrations
|
185
205
|
#
|
186
206
|
# @return [BackgroundMigrationsConfig]
|
@@ -188,6 +208,8 @@ module OnlineMigrations
|
|
188
208
|
#
|
189
209
|
attr_reader :background_migrations
|
190
210
|
|
211
|
+
attr_reader :background_schema_migrations
|
212
|
+
|
191
213
|
def initialize
|
192
214
|
@table_renames = {}
|
193
215
|
@column_renames = {}
|
@@ -202,6 +224,7 @@ module OnlineMigrations
|
|
202
224
|
)
|
203
225
|
|
204
226
|
@background_migrations = BackgroundMigrations::Config.new
|
227
|
+
@background_schema_migrations = BackgroundSchemaMigrations::Config.new
|
205
228
|
|
206
229
|
@checks = []
|
207
230
|
@start_after = 0
|
@@ -213,6 +236,7 @@ module OnlineMigrations
|
|
213
236
|
@enabled_checks = @error_messages.keys.index_with({})
|
214
237
|
@verbose_sql_logs = defined?(Rails.env) && (Rails.env.production? || Rails.env.staging?)
|
215
238
|
@run_background_migrations_inline = -> { Utils.developer_env? }
|
239
|
+
@throttler = -> { false }
|
216
240
|
end
|
217
241
|
|
218
242
|
def lock_retrier=(value)
|
@@ -223,6 +247,14 @@ module OnlineMigrations
|
|
223
247
|
@small_tables = table_names.map(&:to_s)
|
224
248
|
end
|
225
249
|
|
250
|
+
def throttler=(value)
|
251
|
+
if !value.respond_to?(:call)
|
252
|
+
raise ArgumentError, "throttler must be a callable."
|
253
|
+
end
|
254
|
+
|
255
|
+
@throttler = value
|
256
|
+
end
|
257
|
+
|
226
258
|
# Enables specific check
|
227
259
|
#
|
228
260
|
# For the list of available checks look at the `error_messages.rb` file.
|
@@ -135,6 +135,13 @@ module OnlineMigrations
|
|
135
135
|
connection.select_value(query) == "v"
|
136
136
|
end
|
137
137
|
|
138
|
+
def find_connection_class(model)
|
139
|
+
model.ancestors.find do |parent|
|
140
|
+
parent == ActiveRecord::Base ||
|
141
|
+
(parent.is_a?(Class) && parent.abstract_class?)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
138
145
|
def shard_names(model)
|
139
146
|
model.ancestors.each do |ancestor|
|
140
147
|
# There is no official method to get shard names from the model.
|
data/lib/online_migrations.rb
CHANGED
@@ -4,6 +4,7 @@ require "active_record"
|
|
4
4
|
|
5
5
|
require "online_migrations/version"
|
6
6
|
require "online_migrations/utils"
|
7
|
+
require "online_migrations/background_schema_migrations/migration_helpers"
|
7
8
|
require "online_migrations/change_column_type_helpers"
|
8
9
|
require "online_migrations/background_migrations/migration_helpers"
|
9
10
|
require "online_migrations/schema_statements"
|
@@ -22,6 +23,7 @@ module OnlineMigrations
|
|
22
23
|
|
23
24
|
extend ActiveSupport::Autoload
|
24
25
|
|
26
|
+
autoload :ApplicationRecord
|
25
27
|
autoload :BatchIterator
|
26
28
|
autoload :VerboseSqlLogs
|
27
29
|
autoload :ForeignKeysCollector
|
@@ -52,7 +54,6 @@ module OnlineMigrations
|
|
52
54
|
autoload :DeleteOrphanedRecords
|
53
55
|
autoload :PerformActionOnRelation
|
54
56
|
autoload :ResetCounters
|
55
|
-
autoload :ApplicationRecord
|
56
57
|
autoload :MigrationJob
|
57
58
|
autoload :Migration
|
58
59
|
autoload :MigrationJobRunner
|
@@ -60,6 +61,16 @@ module OnlineMigrations
|
|
60
61
|
autoload :Scheduler
|
61
62
|
end
|
62
63
|
|
64
|
+
module BackgroundSchemaMigrations
|
65
|
+
extend ActiveSupport::Autoload
|
66
|
+
|
67
|
+
autoload :Config
|
68
|
+
autoload :Migration
|
69
|
+
autoload :MigrationStatusValidator
|
70
|
+
autoload :MigrationRunner
|
71
|
+
autoload :Scheduler
|
72
|
+
end
|
73
|
+
|
63
74
|
class << self
|
64
75
|
# @private
|
65
76
|
attr_accessor :current_migration
|
@@ -72,10 +83,16 @@ module OnlineMigrations
|
|
72
83
|
@config ||= Config.new
|
73
84
|
end
|
74
85
|
|
75
|
-
# Run background migrations
|
86
|
+
# Run background data migrations
|
76
87
|
def run_background_migrations
|
77
88
|
BackgroundMigrations::Scheduler.run
|
78
89
|
end
|
90
|
+
alias run_background_data_migrations run_background_migrations
|
91
|
+
|
92
|
+
# Run background schema migrations
|
93
|
+
def run_background_schema_migrations
|
94
|
+
BackgroundSchemaMigrations::Scheduler.run
|
95
|
+
end
|
79
96
|
|
80
97
|
def deprecator
|
81
98
|
@deprecator ||=
|
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.16.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-03-
|
11
|
+
date: 2024-03-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -34,19 +34,21 @@ files:
|
|
34
34
|
- CHANGELOG.md
|
35
35
|
- LICENSE.txt
|
36
36
|
- README.md
|
37
|
-
- docs/
|
37
|
+
- docs/background_data_migrations.md
|
38
|
+
- docs/background_schema_migrations.md
|
38
39
|
- docs/configuring.md
|
39
40
|
- lib/generators/online_migrations/background_migration_generator.rb
|
40
41
|
- lib/generators/online_migrations/install_generator.rb
|
41
42
|
- lib/generators/online_migrations/templates/add_sharding_to_online_migrations.rb.tt
|
42
43
|
- lib/generators/online_migrations/templates/background_migration.rb.tt
|
44
|
+
- lib/generators/online_migrations/templates/create_background_schema_migrations.rb.tt
|
43
45
|
- lib/generators/online_migrations/templates/initializer.rb.tt
|
44
46
|
- lib/generators/online_migrations/templates/install_migration.rb.tt
|
45
47
|
- lib/generators/online_migrations/templates/migration.rb.tt
|
46
48
|
- lib/generators/online_migrations/upgrade_generator.rb
|
47
49
|
- lib/online_migrations.rb
|
50
|
+
- lib/online_migrations/application_record.rb
|
48
51
|
- lib/online_migrations/background_migration.rb
|
49
|
-
- lib/online_migrations/background_migrations/application_record.rb
|
50
52
|
- lib/online_migrations/background_migrations/backfill_column.rb
|
51
53
|
- lib/online_migrations/background_migrations/background_migration_class_validator.rb
|
52
54
|
- lib/online_migrations/background_migrations/config.rb
|
@@ -63,6 +65,12 @@ files:
|
|
63
65
|
- lib/online_migrations/background_migrations/perform_action_on_relation.rb
|
64
66
|
- lib/online_migrations/background_migrations/reset_counters.rb
|
65
67
|
- lib/online_migrations/background_migrations/scheduler.rb
|
68
|
+
- lib/online_migrations/background_schema_migrations/config.rb
|
69
|
+
- lib/online_migrations/background_schema_migrations/migration.rb
|
70
|
+
- lib/online_migrations/background_schema_migrations/migration_helpers.rb
|
71
|
+
- lib/online_migrations/background_schema_migrations/migration_runner.rb
|
72
|
+
- lib/online_migrations/background_schema_migrations/migration_status_validator.rb
|
73
|
+
- lib/online_migrations/background_schema_migrations/scheduler.rb
|
66
74
|
- lib/online_migrations/batch_iterator.rb
|
67
75
|
- lib/online_migrations/change_column_type_helpers.rb
|
68
76
|
- lib/online_migrations/command_checker.rb
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OnlineMigrations
|
4
|
-
module BackgroundMigrations
|
5
|
-
# Base class for all records used by this gem.
|
6
|
-
#
|
7
|
-
# Can be extended to setup different database where all tables related to
|
8
|
-
# online_migrations will live.
|
9
|
-
class ApplicationRecord < ActiveRecord::Base
|
10
|
-
self.abstract_class = true
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|