online_migrations 0.15.0 → 0.16.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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +10 -6
  4. data/docs/{background_migrations.md → background_data_migrations.md} +4 -33
  5. data/docs/background_schema_migrations.md +163 -0
  6. data/docs/configuring.md +30 -1
  7. data/lib/generators/online_migrations/templates/create_background_schema_migrations.rb.tt +29 -0
  8. data/lib/generators/online_migrations/templates/initializer.rb.tt +24 -11
  9. data/lib/generators/online_migrations/templates/install_migration.rb.tt +24 -0
  10. data/lib/generators/online_migrations/upgrade_generator.rb +5 -0
  11. data/lib/online_migrations/application_record.rb +11 -0
  12. data/lib/online_migrations/background_migrations/config.rb +27 -24
  13. data/lib/online_migrations/background_migrations/copy_column.rb +1 -4
  14. data/lib/online_migrations/background_migrations/migration.rb +1 -8
  15. data/lib/online_migrations/background_migrations/migration_job_runner.rb +1 -1
  16. data/lib/online_migrations/background_migrations/migration_runner.rb +1 -1
  17. data/lib/online_migrations/background_schema_migrations/config.rb +40 -0
  18. data/lib/online_migrations/background_schema_migrations/migration.rb +217 -0
  19. data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +76 -0
  20. data/lib/online_migrations/background_schema_migrations/migration_runner.rb +110 -0
  21. data/lib/online_migrations/background_schema_migrations/migration_status_validator.rb +33 -0
  22. data/lib/online_migrations/background_schema_migrations/scheduler.rb +30 -0
  23. data/lib/online_migrations/config.rb +32 -0
  24. data/lib/online_migrations/schema_statements.rb +1 -0
  25. data/lib/online_migrations/utils.rb +7 -0
  26. data/lib/online_migrations/version.rb +1 -1
  27. data/lib/online_migrations.rb +19 -2
  28. metadata +12 -4
  29. data/lib/online_migrations/background_migrations/application_record.rb +0 -13
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnlineMigrations
4
+ module BackgroundSchemaMigrations
5
+ # Class representing configuration options for background schema migrations.
6
+ class Config
7
+ # Maximum number of run attempts
8
+ #
9
+ # When attempts are exhausted, the migration is marked as failed.
10
+ # @return [Integer] defaults to 5
11
+ #
12
+ attr_accessor :max_attempts
13
+
14
+ # Statement timeout value used when running background schema migration.
15
+ #
16
+ # @return [Integer] defaults to 1 hour
17
+ #
18
+ attr_accessor :statement_timeout
19
+
20
+ # The callback to perform when an error occurs in the migration.
21
+ #
22
+ # @example
23
+ # OnlineMigrations.config.background_schema_migrations.error_handler = ->(error, errored_migration) do
24
+ # Bugsnag.notify(error) do |notification|
25
+ # notification.add_metadata(:background_schema_migration, { name: errored_migration.name })
26
+ # end
27
+ # end
28
+ #
29
+ # @return [Proc] the callback to perform when an error occurs in the migration
30
+ #
31
+ attr_accessor :error_handler
32
+
33
+ def initialize
34
+ @max_attempts = 5
35
+ @statement_timeout = 1.hour
36
+ @error_handler = ->(error, errored_migration) {}
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,217 @@
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 :table_name, presence: true, length: { maximum: MAX_IDENTIFIER_LENGTH }
61
+ validates :definition, presence: true
62
+ validates :migration_name, presence: true, uniqueness: {
63
+ scope: :shard,
64
+ message: ->(object, data) do
65
+ message = "(#{data[:value]}) has already been taken."
66
+ if object.index_addition?
67
+ message += " Consider enqueuing index creation with a different index name via a `:name` option."
68
+ end
69
+ message
70
+ end,
71
+ }
72
+
73
+ validate :validate_children_statuses, if: -> { composite? && status_changed? }
74
+ validate :validate_connection_class, if: :connection_class_name?
75
+ validate :validate_table_exists
76
+ validates_with MigrationStatusValidator, on: :update
77
+
78
+ before_validation :set_defaults
79
+
80
+ def completed?
81
+ succeeded? || failed?
82
+ end
83
+
84
+ # Returns the progress of the background schema migration.
85
+ #
86
+ # @return [Float] value in range from 0.0 to 100.0
87
+ #
88
+ def progress
89
+ if succeeded?
90
+ 100.0
91
+ elsif composite?
92
+ progresses = children.map(&:progress)
93
+ (progresses.sum.to_f / progresses.size).round(2)
94
+ else
95
+ 0.0
96
+ end
97
+ end
98
+
99
+ # Mark this migration as ready to be processed again.
100
+ #
101
+ # This is used to manually retrying failed migrations.
102
+ #
103
+ def retry
104
+ if composite?
105
+ children.failed.each(&:retry)
106
+ elsif failed?
107
+ update!(
108
+ status: self.class.statuses[:enqueued],
109
+ attempts: 0,
110
+ started_at: nil,
111
+ finished_at: nil,
112
+ error_class: nil,
113
+ error_message: nil,
114
+ backtrace: nil
115
+ )
116
+ end
117
+ end
118
+
119
+ def index_addition?
120
+ definition.match?(/create (unique )?index/i)
121
+ end
122
+
123
+ # @private
124
+ def connection_class
125
+ if connection_class_name && (klass = connection_class_name.safe_constantize)
126
+ Utils.find_connection_class(klass)
127
+ else
128
+ ActiveRecord::Base
129
+ end
130
+ end
131
+
132
+ # @private
133
+ def attempts_exceeded?
134
+ attempts >= max_attempts
135
+ end
136
+
137
+ # @private
138
+ def run
139
+ on_shard do
140
+ connection = connection_class.connection
141
+
142
+ connection.with_lock_retries do
143
+ statement_timeout = self.statement_timeout || OnlineMigrations.config.statement_timeout
144
+
145
+ with_statement_timeout(connection, statement_timeout) do
146
+ if index_addition?
147
+ index = connection.indexes(table_name).find { |i| i.name == name }
148
+ if index
149
+ # Use index validity from https://github.com/rails/rails/pull/45160
150
+ # when switching to ActiveRecord >= 7.1.
151
+ schema = connection.send(:__schema_for_table, table_name)
152
+ if connection.send(:__index_valid?, name, schema: schema)
153
+ return
154
+ else
155
+ connection.remove_index(table_name, name: name)
156
+ end
157
+ end
158
+ end
159
+
160
+ connection.execute(definition)
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ private
167
+ def validate_children_statuses
168
+ if composite?
169
+ if succeeded? && children.except_succeeded.exists?
170
+ errors.add(:base, "all child migrations must be succeeded")
171
+ elsif failed? && !children.failed.exists?
172
+ errors.add(:base, "at least one child migration must be failed")
173
+ end
174
+ end
175
+ end
176
+
177
+ def validate_connection_class
178
+ klass = connection_class_name.safe_constantize
179
+ if !(klass < ActiveRecord::Base)
180
+ errors.add(:connection_class_name, "is not an ActiveRecord::Base child class")
181
+ end
182
+ end
183
+
184
+ def validate_table_exists
185
+ # Skip this validation if we have invalid connection class name.
186
+ return if errors.include?(:connection_class_name)
187
+
188
+ on_shard do
189
+ if !connection_class.connection.table_exists?(table_name)
190
+ errors.add(:table_name, "'#{table_name}' does not exist")
191
+ end
192
+ end
193
+ end
194
+
195
+ def set_defaults
196
+ config = ::OnlineMigrations.config.background_schema_migrations
197
+ self.max_attempts ||= config.max_attempts
198
+ self.statement_timeout ||= config.statement_timeout
199
+ end
200
+
201
+ def on_shard(&block)
202
+ shard = (self.shard || connection_class.default_shard).to_sym
203
+ connection_class.connected_to(shard: shard, role: :writing, &block)
204
+ end
205
+
206
+ def with_statement_timeout(connection, timeout)
207
+ return yield if timeout.nil?
208
+
209
+ prev_value = connection.select_value("SHOW statement_timeout")
210
+ connection.execute("SET statement_timeout TO #{connection.quote(timeout.in_milliseconds)}")
211
+ yield
212
+ ensure
213
+ connection.execute("SET statement_timeout TO #{connection.quote(prev_value)}")
214
+ end
215
+ end
216
+ end
217
+ 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.
@@ -4,6 +4,7 @@ module OnlineMigrations
4
4
  module SchemaStatements
5
5
  include ChangeColumnTypeHelpers
6
6
  include BackgroundMigrations::MigrationHelpers
7
+ include BackgroundSchemaMigrations::MigrationHelpers
7
8
 
8
9
  # Updates the value of a column in batches.
9
10
  #
@@ -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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OnlineMigrations
4
- VERSION = "0.15.0"
4
+ VERSION = "0.16.1"
5
5
  end
@@ -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 ||=