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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/docs/0.27-upgrade.md +24 -0
  4. data/docs/background_data_migrations.md +200 -101
  5. data/docs/background_schema_migrations.md +2 -2
  6. data/lib/generators/online_migrations/{background_migration_generator.rb → data_migration_generator.rb} +4 -4
  7. data/lib/generators/online_migrations/templates/change_background_data_migrations.rb.tt +34 -0
  8. data/lib/generators/online_migrations/templates/{background_data_migration.rb.tt → data_migration.rb.tt} +8 -9
  9. data/lib/generators/online_migrations/templates/initializer.rb.tt +19 -25
  10. data/lib/generators/online_migrations/templates/install_migration.rb.tt +9 -40
  11. data/lib/generators/online_migrations/upgrade_generator.rb +16 -8
  12. data/lib/online_migrations/active_record_batch_enumerator.rb +8 -0
  13. data/lib/online_migrations/background_data_migrations/backfill_column.rb +50 -0
  14. data/lib/online_migrations/background_data_migrations/config.rb +62 -0
  15. data/lib/online_migrations/{background_migrations → background_data_migrations}/copy_column.rb +15 -28
  16. data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_associated_records.rb +9 -5
  17. data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_orphaned_records.rb +5 -9
  18. data/lib/online_migrations/background_data_migrations/migration.rb +312 -0
  19. data/lib/online_migrations/{background_migrations → background_data_migrations}/migration_helpers.rb +72 -61
  20. data/lib/online_migrations/background_data_migrations/migration_job.rb +158 -0
  21. data/lib/online_migrations/background_data_migrations/migration_status_validator.rb +65 -0
  22. data/lib/online_migrations/{background_migrations → background_data_migrations}/perform_action_on_relation.rb +5 -5
  23. data/lib/online_migrations/{background_migrations → background_data_migrations}/reset_counters.rb +5 -5
  24. data/lib/online_migrations/background_data_migrations/scheduler.rb +78 -0
  25. data/lib/online_migrations/background_data_migrations/ticker.rb +62 -0
  26. data/lib/online_migrations/background_schema_migrations/config.rb +2 -2
  27. data/lib/online_migrations/background_schema_migrations/migration.rb +51 -123
  28. data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +25 -46
  29. data/lib/online_migrations/background_schema_migrations/migration_runner.rb +43 -97
  30. data/lib/online_migrations/background_schema_migrations/scheduler.rb +2 -2
  31. data/lib/online_migrations/change_column_type_helpers.rb +3 -2
  32. data/lib/online_migrations/config.rb +4 -4
  33. data/lib/online_migrations/data_migration.rb +127 -0
  34. data/lib/online_migrations/lock_retrier.rb +5 -2
  35. data/lib/online_migrations/schema_statements.rb +1 -1
  36. data/lib/online_migrations/shard_aware.rb +44 -0
  37. data/lib/online_migrations/version.rb +1 -1
  38. data/lib/online_migrations.rb +18 -11
  39. metadata +22 -21
  40. data/lib/online_migrations/background_migration.rb +0 -64
  41. data/lib/online_migrations/background_migrations/backfill_column.rb +0 -54
  42. data/lib/online_migrations/background_migrations/background_migration_class_validator.rb +0 -29
  43. data/lib/online_migrations/background_migrations/config.rb +0 -74
  44. data/lib/online_migrations/background_migrations/migration.rb +0 -329
  45. data/lib/online_migrations/background_migrations/migration_job.rb +0 -109
  46. data/lib/online_migrations/background_migrations/migration_job_runner.rb +0 -66
  47. data/lib/online_migrations/background_migrations/migration_job_status_validator.rb +0 -29
  48. data/lib/online_migrations/background_migrations/migration_runner.rb +0 -161
  49. data/lib/online_migrations/background_migrations/migration_status_validator.rb +0 -48
  50. 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.26.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-04-27 00:00:00.000000000 Z
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/background_migration_generator.rb
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/background_migration.rb
54
- - lib/online_migrations/background_migrations/backfill_column.rb
55
- - lib/online_migrations/background_migrations/background_migration_class_validator.rb
56
- - lib/online_migrations/background_migrations/config.rb
57
- - lib/online_migrations/background_migrations/copy_column.rb
58
- - lib/online_migrations/background_migrations/delete_associated_records.rb
59
- - lib/online_migrations/background_migrations/delete_orphaned_records.rb
60
- - lib/online_migrations/background_migrations/migration.rb
61
- - lib/online_migrations/background_migrations/migration_helpers.rb
62
- - lib/online_migrations/background_migrations/migration_job.rb
63
- - lib/online_migrations/background_migrations/migration_job_runner.rb
64
- - lib/online_migrations/background_migrations/migration_job_status_validator.rb
65
- - lib/online_migrations/background_migrations/migration_runner.rb
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