online_migrations 0.26.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 +14 -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 +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 +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 +3 -2
- data/lib/online_migrations/config.rb +4 -4
- data/lib/online_migrations/data_migration.rb +127 -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
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.27.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- fatkodima
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-05-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -34,39 +34,38 @@ files:
|
|
34
34
|
- CHANGELOG.md
|
35
35
|
- LICENSE.txt
|
36
36
|
- README.md
|
37
|
+
- docs/0.27-upgrade.md
|
37
38
|
- docs/background_data_migrations.md
|
38
39
|
- docs/background_schema_migrations.md
|
39
40
|
- docs/configuring.md
|
40
|
-
- lib/generators/online_migrations/
|
41
|
+
- lib/generators/online_migrations/data_migration_generator.rb
|
41
42
|
- lib/generators/online_migrations/install_generator.rb
|
42
43
|
- lib/generators/online_migrations/templates/add_sharding_to_online_migrations.rb.tt
|
43
44
|
- lib/generators/online_migrations/templates/add_timestamps_to_background_migrations.rb.tt
|
44
|
-
- lib/generators/online_migrations/templates/background_data_migration.rb.tt
|
45
45
|
- lib/generators/online_migrations/templates/background_schema_migrations_change_unique_index.rb.tt
|
46
|
+
- lib/generators/online_migrations/templates/change_background_data_migrations.rb.tt
|
46
47
|
- lib/generators/online_migrations/templates/create_background_schema_migrations.rb.tt
|
48
|
+
- lib/generators/online_migrations/templates/data_migration.rb.tt
|
47
49
|
- lib/generators/online_migrations/templates/initializer.rb.tt
|
48
50
|
- lib/generators/online_migrations/templates/install_migration.rb.tt
|
49
51
|
- lib/generators/online_migrations/templates/migration.rb.tt
|
50
52
|
- lib/generators/online_migrations/upgrade_generator.rb
|
51
53
|
- lib/online_migrations.rb
|
54
|
+
- lib/online_migrations/active_record_batch_enumerator.rb
|
52
55
|
- lib/online_migrations/application_record.rb
|
53
|
-
- lib/online_migrations/
|
54
|
-
- lib/online_migrations/
|
55
|
-
- lib/online_migrations/
|
56
|
-
- lib/online_migrations/
|
57
|
-
- lib/online_migrations/
|
58
|
-
- lib/online_migrations/
|
59
|
-
- lib/online_migrations/
|
60
|
-
- lib/online_migrations/
|
61
|
-
- lib/online_migrations/
|
62
|
-
- lib/online_migrations/
|
63
|
-
- lib/online_migrations/
|
64
|
-
- lib/online_migrations/
|
65
|
-
- lib/online_migrations/
|
66
|
-
- lib/online_migrations/background_migrations/migration_status_validator.rb
|
67
|
-
- lib/online_migrations/background_migrations/perform_action_on_relation.rb
|
68
|
-
- lib/online_migrations/background_migrations/reset_counters.rb
|
69
|
-
- lib/online_migrations/background_migrations/scheduler.rb
|
56
|
+
- lib/online_migrations/background_data_migrations/backfill_column.rb
|
57
|
+
- lib/online_migrations/background_data_migrations/config.rb
|
58
|
+
- lib/online_migrations/background_data_migrations/copy_column.rb
|
59
|
+
- lib/online_migrations/background_data_migrations/delete_associated_records.rb
|
60
|
+
- lib/online_migrations/background_data_migrations/delete_orphaned_records.rb
|
61
|
+
- lib/online_migrations/background_data_migrations/migration.rb
|
62
|
+
- lib/online_migrations/background_data_migrations/migration_helpers.rb
|
63
|
+
- lib/online_migrations/background_data_migrations/migration_job.rb
|
64
|
+
- lib/online_migrations/background_data_migrations/migration_status_validator.rb
|
65
|
+
- lib/online_migrations/background_data_migrations/perform_action_on_relation.rb
|
66
|
+
- lib/online_migrations/background_data_migrations/reset_counters.rb
|
67
|
+
- lib/online_migrations/background_data_migrations/scheduler.rb
|
68
|
+
- lib/online_migrations/background_data_migrations/ticker.rb
|
70
69
|
- lib/online_migrations/background_schema_migrations/config.rb
|
71
70
|
- lib/online_migrations/background_schema_migrations/migration.rb
|
72
71
|
- lib/online_migrations/background_schema_migrations/migration_helpers.rb
|
@@ -79,6 +78,7 @@ files:
|
|
79
78
|
- lib/online_migrations/command_recorder.rb
|
80
79
|
- lib/online_migrations/config.rb
|
81
80
|
- lib/online_migrations/copy_trigger.rb
|
81
|
+
- lib/online_migrations/data_migration.rb
|
82
82
|
- lib/online_migrations/database_tasks.rb
|
83
83
|
- lib/online_migrations/error_messages.rb
|
84
84
|
- lib/online_migrations/foreign_keys_collector.rb
|
@@ -89,6 +89,7 @@ files:
|
|
89
89
|
- lib/online_migrations/schema_cache.rb
|
90
90
|
- lib/online_migrations/schema_dumper.rb
|
91
91
|
- lib/online_migrations/schema_statements.rb
|
92
|
+
- lib/online_migrations/shard_aware.rb
|
92
93
|
- lib/online_migrations/utils.rb
|
93
94
|
- lib/online_migrations/verbose_sql_logs.rb
|
94
95
|
- lib/online_migrations/version.rb
|
@@ -1,64 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OnlineMigrations
|
4
|
-
# Base class that is inherited by the host application's background migration classes.
|
5
|
-
class BackgroundMigration
|
6
|
-
class NotFoundError < NameError; end
|
7
|
-
|
8
|
-
class << self
|
9
|
-
# Finds a Background Migration with the given name.
|
10
|
-
#
|
11
|
-
# @param name [String] the name of the Background Migration to be found.
|
12
|
-
#
|
13
|
-
# @return [BackgroundMigration] the Background Migration with the given name.
|
14
|
-
#
|
15
|
-
# @raise [NotFoundError] if a Background Migration with the given name does not exist.
|
16
|
-
#
|
17
|
-
def named(name)
|
18
|
-
namespace = OnlineMigrations.config.background_migrations.migrations_module.constantize
|
19
|
-
internal_namespace = ::OnlineMigrations::BackgroundMigrations
|
20
|
-
|
21
|
-
migration = "#{namespace}::#{name}".safe_constantize ||
|
22
|
-
"#{internal_namespace}::#{name}".safe_constantize
|
23
|
-
|
24
|
-
raise NotFoundError.new("Background Migration #{name} not found", name) if migration.nil?
|
25
|
-
if !(migration.is_a?(Class) && migration < self)
|
26
|
-
raise NotFoundError.new("#{name} is not a Background Migration", name)
|
27
|
-
end
|
28
|
-
|
29
|
-
migration
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# The relation to be iterated over.
|
34
|
-
#
|
35
|
-
# @return [ActiveRecord::Relation]
|
36
|
-
#
|
37
|
-
# @raise [NotImplementedError] with a message advising subclasses to
|
38
|
-
# implement an override for this method.
|
39
|
-
#
|
40
|
-
def relation
|
41
|
-
raise NotImplementedError, "#{self.class.name} must implement a 'relation' method"
|
42
|
-
end
|
43
|
-
|
44
|
-
# Processes one batch.
|
45
|
-
#
|
46
|
-
# @param _relation [ActiveRecord::Relation] the current batch from the enumerator being iterated
|
47
|
-
# @return [void]
|
48
|
-
#
|
49
|
-
# @raise [NotImplementedError] with a message advising subclasses to
|
50
|
-
# implement an override for this method.
|
51
|
-
#
|
52
|
-
def process_batch(_relation)
|
53
|
-
raise NotImplementedError, "#{self.class.name} must implement a 'process_batch' method"
|
54
|
-
end
|
55
|
-
|
56
|
-
# Returns the count of rows that will be iterated over (optional, to be able to show progress).
|
57
|
-
#
|
58
|
-
# @return [Integer, nil, :no_count]
|
59
|
-
#
|
60
|
-
def count
|
61
|
-
:no_count
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
@@ -1,54 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OnlineMigrations
|
4
|
-
module BackgroundMigrations
|
5
|
-
# @private
|
6
|
-
class BackfillColumn < BackgroundMigration
|
7
|
-
attr_reader :table_name, :updates, :model_name
|
8
|
-
|
9
|
-
def initialize(table_name, updates, model_name = nil)
|
10
|
-
@table_name = table_name
|
11
|
-
@updates = updates
|
12
|
-
@model_name = model_name
|
13
|
-
end
|
14
|
-
|
15
|
-
def relation
|
16
|
-
column, value = updates.first
|
17
|
-
|
18
|
-
if updates.size == 1 && !value.nil?
|
19
|
-
# If value is nil, the generated SQL is correct (`WHERE column IS NOT NULL`).
|
20
|
-
# Otherwise, the SQL is `WHERE column != value`. This condition ignores column
|
21
|
-
# with NULLs in it, so we need to also manually check for NULLs.
|
22
|
-
quoted_column = connection.quote_column_name(column)
|
23
|
-
model.unscoped.where("#{quoted_column} != ? OR #{quoted_column} IS NULL", value)
|
24
|
-
else
|
25
|
-
model.unscoped.where.not(updates)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def process_batch(relation)
|
30
|
-
relation.update_all(updates)
|
31
|
-
end
|
32
|
-
|
33
|
-
def count
|
34
|
-
# Exact counts are expensive on large tables, since PostgreSQL
|
35
|
-
# needs to do a full scan. An estimated count should give a pretty decent
|
36
|
-
# approximation of rows count in this case.
|
37
|
-
Utils.estimated_count(connection, table_name)
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
def model
|
42
|
-
@model ||= if model_name.present?
|
43
|
-
Object.const_get(model_name, false)
|
44
|
-
else
|
45
|
-
Utils.define_model(table_name)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def connection
|
50
|
-
model.connection
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OnlineMigrations
|
4
|
-
module BackgroundMigrations
|
5
|
-
# @private
|
6
|
-
class BackgroundMigrationClassValidator < ActiveModel::Validator
|
7
|
-
def validate(record)
|
8
|
-
relation = record.migration_relation
|
9
|
-
migration_name = record.migration_name
|
10
|
-
|
11
|
-
if !relation.is_a?(ActiveRecord::Relation)
|
12
|
-
record.errors.add(
|
13
|
-
:migration_name,
|
14
|
-
"#{migration_name}#relation must return an ActiveRecord::Relation object"
|
15
|
-
)
|
16
|
-
return
|
17
|
-
end
|
18
|
-
|
19
|
-
if relation.arel.orders.present? || relation.arel.taken.present?
|
20
|
-
record.errors.add(
|
21
|
-
:migration_name,
|
22
|
-
"#{migration_name}#relation cannot use ORDER BY or LIMIT due to the way how iteration with a cursor is designed. " \
|
23
|
-
"You can use other ways to limit the number of rows, e.g. a WHERE condition on the primary key column."
|
24
|
-
)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
@@ -1,74 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OnlineMigrations
|
4
|
-
module BackgroundMigrations
|
5
|
-
# Class representing configuration options for background migrations.
|
6
|
-
class Config
|
7
|
-
# The path where generated background migrations will be placed
|
8
|
-
# @return [String] defaults to "lib"
|
9
|
-
attr_accessor :migrations_path
|
10
|
-
|
11
|
-
# The module in which background migrations will be placed
|
12
|
-
# @return [String] defaults to "OnlineMigrations::BackgroundMigrations"
|
13
|
-
attr_accessor :migrations_module
|
14
|
-
|
15
|
-
# The number of rows to process in a single background migration run
|
16
|
-
# @return [Integer] defaults to 1_000
|
17
|
-
#
|
18
|
-
attr_accessor :batch_size
|
19
|
-
|
20
|
-
# The smaller batches size that the batches will be divided into
|
21
|
-
# @return [Integer] defaults to 100
|
22
|
-
#
|
23
|
-
attr_accessor :sub_batch_size
|
24
|
-
|
25
|
-
# The pause interval between each background migration job's execution (in seconds)
|
26
|
-
# @return [Integer] defaults to 0
|
27
|
-
#
|
28
|
-
attr_accessor :batch_pause
|
29
|
-
|
30
|
-
# The number of milliseconds to sleep between each sub_batch execution
|
31
|
-
# @return [Integer] defaults to 100 milliseconds
|
32
|
-
#
|
33
|
-
attr_accessor :sub_batch_pause_ms
|
34
|
-
|
35
|
-
# Maximum number of batch run attempts
|
36
|
-
#
|
37
|
-
# When attempts are exhausted, the individual batch is marked as failed.
|
38
|
-
# @return [Integer] defaults to 5
|
39
|
-
#
|
40
|
-
attr_accessor :batch_max_attempts
|
41
|
-
|
42
|
-
# The number of seconds that must pass before the running job is considered stuck
|
43
|
-
#
|
44
|
-
# @return [Integer] defaults to 1 hour
|
45
|
-
#
|
46
|
-
attr_accessor :stuck_jobs_timeout
|
47
|
-
|
48
|
-
# The callback to perform when an error occurs in the migration job.
|
49
|
-
#
|
50
|
-
# @example
|
51
|
-
# OnlineMigrations.config.background_migrations.error_handler = ->(error, errored_job) do
|
52
|
-
# Bugsnag.notify(error) do |notification|
|
53
|
-
# notification.add_metadata(:background_migration, { name: errored_job.migration_name })
|
54
|
-
# end
|
55
|
-
# end
|
56
|
-
#
|
57
|
-
# @return [Proc] the callback to perform when an error occurs in the migration job
|
58
|
-
#
|
59
|
-
attr_accessor :error_handler
|
60
|
-
|
61
|
-
def initialize
|
62
|
-
@migrations_path = "lib"
|
63
|
-
@migrations_module = "OnlineMigrations::BackgroundMigrations"
|
64
|
-
@batch_size = 1_000
|
65
|
-
@sub_batch_size = 100
|
66
|
-
@batch_pause = 0.seconds
|
67
|
-
@sub_batch_pause_ms = 100
|
68
|
-
@batch_max_attempts = 5
|
69
|
-
@stuck_jobs_timeout = 1.hour
|
70
|
-
@error_handler = ->(error, errored_job) {}
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
@@ -1,329 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OnlineMigrations
|
4
|
-
module BackgroundMigrations
|
5
|
-
# Class representing background data migration.
|
6
|
-
#
|
7
|
-
# @note The records of this class should not be created manually, but via
|
8
|
-
# `enqueue_background_data_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
|
-
:paused, # The migration was paused in the middle of the run by the user.
|
15
|
-
:finishing, # The migration is being manually finishing inline by the user.
|
16
|
-
:failed, # The migration raises an exception when running.
|
17
|
-
:succeeded, # The migration finished without error.
|
18
|
-
:cancelled, # The migration was cancelled by the user.
|
19
|
-
]
|
20
|
-
|
21
|
-
self.table_name = :background_migrations
|
22
|
-
|
23
|
-
scope :queue_order, -> { order(created_at: :asc) }
|
24
|
-
scope :parents, -> { where(parent_id: nil) }
|
25
|
-
scope :runnable, -> { where(composite: false) }
|
26
|
-
scope :active, -> { where(status: [statuses[:enqueued], statuses[:running]]) }
|
27
|
-
scope :except_succeeded, -> { where.not(status: :succeeded) }
|
28
|
-
scope :for_migration_name, ->(migration_name) { where(migration_name: normalize_migration_name(migration_name)) }
|
29
|
-
scope :for_configuration, ->(migration_name, arguments) do
|
30
|
-
for_migration_name(migration_name).where("arguments = ?", arguments.to_json)
|
31
|
-
end
|
32
|
-
|
33
|
-
alias_attribute :name, :migration_name
|
34
|
-
|
35
|
-
enum :status, STATUSES.index_with(&:to_s)
|
36
|
-
|
37
|
-
belongs_to :parent, class_name: name, optional: true, inverse_of: :children
|
38
|
-
has_many :children, class_name: name, foreign_key: :parent_id, dependent: :delete_all, inverse_of: :parent
|
39
|
-
has_many :migration_jobs, dependent: :delete_all, inverse_of: :migration
|
40
|
-
|
41
|
-
validates :migration_name, :batch_column_name, presence: true
|
42
|
-
|
43
|
-
validates :batch_size, :sub_batch_size, presence: true, numericality: { greater_than: 0 }
|
44
|
-
validates :min_value, :max_value, presence: true, numericality: { greater_than: 0, unless: :composite? }
|
45
|
-
|
46
|
-
validates :batch_pause, :sub_batch_pause_ms, presence: true,
|
47
|
-
numericality: { greater_than_or_equal_to: 0 }
|
48
|
-
validates :rows_count, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true, unless: :composite?
|
49
|
-
validates :arguments, uniqueness: { scope: [:migration_name, :shard] }
|
50
|
-
|
51
|
-
validate :validate_batch_column_values
|
52
|
-
validate :validate_batch_sizes
|
53
|
-
validate :validate_jobs_status, if: :status_changed?
|
54
|
-
|
55
|
-
validates_with BackgroundMigrationClassValidator
|
56
|
-
validates_with MigrationStatusValidator, on: :update
|
57
|
-
|
58
|
-
before_validation :set_defaults
|
59
|
-
before_update :copy_attributes_to_children, if: :composite?
|
60
|
-
|
61
|
-
# @private
|
62
|
-
def self.normalize_migration_name(migration_name)
|
63
|
-
namespace = ::OnlineMigrations.config.background_migrations.migrations_module
|
64
|
-
migration_name.sub(/^(::)?#{namespace}::/, "")
|
65
|
-
end
|
66
|
-
|
67
|
-
def migration_name=(class_name)
|
68
|
-
class_name = class_name.name if class_name.is_a?(Class)
|
69
|
-
write_attribute(:migration_name, self.class.normalize_migration_name(class_name))
|
70
|
-
end
|
71
|
-
alias name= migration_name=
|
72
|
-
|
73
|
-
def completed?
|
74
|
-
succeeded? || failed?
|
75
|
-
end
|
76
|
-
|
77
|
-
# Overwrite enum's generated method to correctly work for composite migrations.
|
78
|
-
def paused!
|
79
|
-
return super if !composite?
|
80
|
-
|
81
|
-
transaction do
|
82
|
-
super
|
83
|
-
children.each { |child| child.paused! if child.enqueued? || child.running? }
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
# Overwrite enum's generated method to correctly work for composite migrations.
|
88
|
-
def running!
|
89
|
-
return super if !composite?
|
90
|
-
|
91
|
-
transaction do
|
92
|
-
super
|
93
|
-
children.each { |child| child.running! if child.paused? }
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
# Overwrite enum's generated method to correctly work for composite migrations.
|
98
|
-
def cancelled!
|
99
|
-
return super if !composite?
|
100
|
-
|
101
|
-
transaction do
|
102
|
-
super
|
103
|
-
children.each { |child| child.cancelled! if !child.succeeded? }
|
104
|
-
end
|
105
|
-
end
|
106
|
-
alias cancel cancelled!
|
107
|
-
|
108
|
-
def pausable?
|
109
|
-
true
|
110
|
-
end
|
111
|
-
|
112
|
-
def can_be_paused?
|
113
|
-
enqueued? || running?
|
114
|
-
end
|
115
|
-
|
116
|
-
def can_be_cancelled?
|
117
|
-
!succeeded? && !cancelled?
|
118
|
-
end
|
119
|
-
|
120
|
-
def last_job
|
121
|
-
migration_jobs.order(:max_value).last
|
122
|
-
end
|
123
|
-
|
124
|
-
# Returns the progress of the background migration.
|
125
|
-
#
|
126
|
-
# @return [Float, nil]
|
127
|
-
# - when background migration is configured to not track progress, returns `nil`
|
128
|
-
# - otherwise returns value in range from 0.0 to 100.0
|
129
|
-
#
|
130
|
-
def progress
|
131
|
-
if succeeded?
|
132
|
-
100.0
|
133
|
-
elsif enqueued?
|
134
|
-
0.0
|
135
|
-
elsif composite?
|
136
|
-
rows_counts = children.to_a.pluck(:rows_count)
|
137
|
-
if rows_counts.none?(nil)
|
138
|
-
total_rows_count = rows_counts.sum
|
139
|
-
return 100.0 if total_rows_count == 0
|
140
|
-
|
141
|
-
progresses = children.map do |child|
|
142
|
-
child.progress * child.rows_count / total_rows_count # weighted progress
|
143
|
-
end
|
144
|
-
|
145
|
-
progresses.sum.round(2)
|
146
|
-
end
|
147
|
-
elsif rows_count
|
148
|
-
if rows_count > 0 && rows_count > batch_size
|
149
|
-
jobs_rows_count = migration_jobs.succeeded.sum(:batch_size)
|
150
|
-
# The last migration job may need to process the amount of rows
|
151
|
-
# less than the batch size, so we can get a value > 1.0.
|
152
|
-
([jobs_rows_count.to_f / rows_count, 1.0].min * 100).round(2)
|
153
|
-
else
|
154
|
-
0.0
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def migration_class
|
160
|
-
BackgroundMigration.named(migration_name)
|
161
|
-
end
|
162
|
-
|
163
|
-
def migration_object
|
164
|
-
@migration_object ||= migration_class.new(*arguments)
|
165
|
-
end
|
166
|
-
|
167
|
-
def migration_relation
|
168
|
-
migration_object.relation
|
169
|
-
end
|
170
|
-
|
171
|
-
def migration_model
|
172
|
-
migration_relation.model
|
173
|
-
end
|
174
|
-
|
175
|
-
# Returns whether the interval between previous step run has passed.
|
176
|
-
# @return [Boolean]
|
177
|
-
#
|
178
|
-
def interval_elapsed?
|
179
|
-
last_job = migration_jobs.order(:updated_at).last
|
180
|
-
return true if last_job.nil?
|
181
|
-
|
182
|
-
last_job.enqueued? || (last_job.updated_at + batch_pause <= Time.current)
|
183
|
-
end
|
184
|
-
|
185
|
-
# Mark this migration as ready to be processed again.
|
186
|
-
#
|
187
|
-
# This method marks failed jobs as ready to be processed again, and
|
188
|
-
# they will be picked up on the next Scheduler run.
|
189
|
-
#
|
190
|
-
def retry
|
191
|
-
if composite? && failed?
|
192
|
-
transaction do
|
193
|
-
update!(status: :enqueued, finished_at: nil)
|
194
|
-
children.failed.each(&:retry)
|
195
|
-
end
|
196
|
-
|
197
|
-
true
|
198
|
-
elsif failed?
|
199
|
-
transaction do
|
200
|
-
parent.update!(status: :enqueued, finished_at: nil) if parent
|
201
|
-
update!(status: :enqueued, started_at: nil, finished_at: nil)
|
202
|
-
|
203
|
-
iterator = BatchIterator.new(migration_jobs.failed)
|
204
|
-
iterator.each_batch(of: 100) do |batch|
|
205
|
-
batch.each(&:retry)
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
true
|
210
|
-
else
|
211
|
-
false
|
212
|
-
end
|
213
|
-
end
|
214
|
-
alias retry_failed_jobs retry
|
215
|
-
|
216
|
-
# @private
|
217
|
-
def on_shard(&block)
|
218
|
-
abstract_class = Utils.find_connection_class(migration_model)
|
219
|
-
|
220
|
-
shard = (self.shard || abstract_class.default_shard).to_sym
|
221
|
-
abstract_class.connected_to(shard: shard, role: :writing, &block)
|
222
|
-
end
|
223
|
-
|
224
|
-
# @private
|
225
|
-
def reset_failed_jobs_attempts
|
226
|
-
iterator = BatchIterator.new(migration_jobs.failed)
|
227
|
-
iterator.each_batch(of: 100) do |relation|
|
228
|
-
relation.update_all(status: :enqueued, attempts: 0)
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
# @private
|
233
|
-
def next_batch_range
|
234
|
-
iterator = BatchIterator.new(migration_relation)
|
235
|
-
batch_range = nil
|
236
|
-
|
237
|
-
on_shard do
|
238
|
-
# rubocop:disable Lint/UnreachableLoop
|
239
|
-
iterator.each_batch(of: batch_size, column: batch_column_name, start: next_min_value, finish: max_value) do |_relation, min_value, max_value|
|
240
|
-
batch_range = [min_value, max_value]
|
241
|
-
|
242
|
-
break
|
243
|
-
end
|
244
|
-
# rubocop:enable Lint/UnreachableLoop
|
245
|
-
end
|
246
|
-
|
247
|
-
return if batch_range.nil?
|
248
|
-
|
249
|
-
min_value, max_value = batch_range
|
250
|
-
return if min_value > self.max_value
|
251
|
-
|
252
|
-
max_value = [max_value, self.max_value].min
|
253
|
-
|
254
|
-
[min_value, max_value]
|
255
|
-
end
|
256
|
-
|
257
|
-
private
|
258
|
-
def validate_batch_column_values
|
259
|
-
if max_value.to_i < min_value.to_i
|
260
|
-
errors.add(:base, "max_value should be greater than or equal to min_value")
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
def validate_batch_sizes
|
265
|
-
if sub_batch_size.to_i > batch_size.to_i
|
266
|
-
errors.add(:base, "sub_batch_size should be smaller than or equal to batch_size")
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
def validate_jobs_status
|
271
|
-
if composite?
|
272
|
-
if succeeded? && children.except_succeeded.exists?
|
273
|
-
errors.add(:base, "all child migrations must be succeeded")
|
274
|
-
elsif failed? && !children.failed.exists?
|
275
|
-
errors.add(:base, "at least one child migration must be failed")
|
276
|
-
end
|
277
|
-
elsif succeeded? && migration_jobs.except_succeeded.exists?
|
278
|
-
errors.add(:base, "all migration jobs must be succeeded")
|
279
|
-
elsif failed? && !migration_jobs.failed.exists?
|
280
|
-
errors.add(:base, "at least one migration job must be failed")
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
def set_defaults
|
285
|
-
if migration_relation.is_a?(ActiveRecord::Relation)
|
286
|
-
self.batch_column_name ||= migration_relation.primary_key
|
287
|
-
|
288
|
-
if composite?
|
289
|
-
self.min_value = self.max_value = self.rows_count = -1 # not relevant
|
290
|
-
else
|
291
|
-
on_shard do
|
292
|
-
# Getting exact min/max values can be a very heavy operation
|
293
|
-
# and is not needed practically.
|
294
|
-
self.min_value ||= 1
|
295
|
-
self.max_value ||= migration_model.unscoped.maximum(batch_column_name) || self.min_value
|
296
|
-
|
297
|
-
count = migration_object.count
|
298
|
-
self.rows_count = count if count != :no_count
|
299
|
-
end
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
config = ::OnlineMigrations.config.background_migrations
|
304
|
-
self.batch_size ||= config.batch_size
|
305
|
-
self.sub_batch_size ||= config.sub_batch_size
|
306
|
-
self.batch_pause ||= config.batch_pause
|
307
|
-
self.sub_batch_pause_ms ||= config.sub_batch_pause_ms
|
308
|
-
self.batch_max_attempts ||= config.batch_max_attempts
|
309
|
-
end
|
310
|
-
|
311
|
-
def copy_attributes_to_children
|
312
|
-
attributes = [:batch_size, :sub_batch_size, :batch_pause, :sub_batch_pause_ms, :batch_max_attempts]
|
313
|
-
updates = {}
|
314
|
-
attributes.each do |attribute|
|
315
|
-
updates[attribute] = read_attribute(attribute) if attribute_changed?(attribute)
|
316
|
-
end
|
317
|
-
children.active.update_all(updates) if updates.any?
|
318
|
-
end
|
319
|
-
|
320
|
-
def next_min_value
|
321
|
-
if last_job
|
322
|
-
last_job.max_value.next
|
323
|
-
else
|
324
|
-
min_value
|
325
|
-
end
|
326
|
-
end
|
327
|
-
end
|
328
|
-
end
|
329
|
-
end
|