Class: OnlineMigrations::BackgroundMigrations::Migration

Inherits:
ApplicationRecord
  • Object
show all
Defined in:
lib/online_migrations/background_migrations/migration.rb

Overview

Note:

The records of this class should not be created manually, but via ‘enqueue_background_data_migration` helper inside migrations.

Class representing background data migration.

Constant Summary collapse

STATUSES =
[
  :enqueued,    # The migration has been enqueued by the user.
  :running,     # The migration is being performed by a migration executor.
  :paused,      # The migration was paused in the middle of the run by the user.
  :finishing,   # The migration is being manually finishing inline by the user.
  :failed,      # The migration raises an exception when running.
  :succeeded,   # The migration finished without error.
  :cancelled,   # The migration was cancelled by the user.
]

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.normalize_migration_name(migration_name) ⇒ Object



67
68
69
70
# File 'lib/online_migrations/background_migrations/migration.rb', line 67

def self.normalize_migration_name(migration_name)
  namespace = ::OnlineMigrations.config.background_migrations.migrations_module
  migration_name.sub(/^(::)?#{namespace}::/, "")
end

Instance Method Details

#cancelled!Object Also known as: cancel

Overwrite enum’s generated method to correctly work for composite migrations.



103
104
105
106
107
108
109
110
# File 'lib/online_migrations/background_migrations/migration.rb', line 103

def cancelled!
  return super if !composite?

  transaction do
    super
    children.each { |child| child.cancelled! if !child.succeeded? }
  end
end

#completed?Boolean

Returns:

  • (Boolean)


78
79
80
# File 'lib/online_migrations/background_migrations/migration.rb', line 78

def completed?
  succeeded? || failed?
end

#finished_atObject

Returns the time this migration finished running.



211
212
213
# File 'lib/online_migrations/background_migrations/migration.rb', line 211

def finished_at
  updated_at if completed?
end

#interval_elapsed?Boolean

Returns whether the interval between previous step run has passed.

Returns:

  • (Boolean)


171
172
173
174
175
176
# File 'lib/online_migrations/background_migrations/migration.rb', line 171

def interval_elapsed?
  last_job = migration_jobs.order(:updated_at).last
  return true if last_job.nil?

  last_job.enqueued? || (last_job.updated_at + batch_pause <= Time.current)
end

#last_jobObject



113
114
115
# File 'lib/online_migrations/background_migrations/migration.rb', line 113

def last_job
  migration_jobs.order(:max_value).last
end

#migration_classObject



152
153
154
# File 'lib/online_migrations/background_migrations/migration.rb', line 152

def migration_class
  BackgroundMigration.named(migration_name)
end

#migration_modelObject



164
165
166
# File 'lib/online_migrations/background_migrations/migration.rb', line 164

def migration_model
  migration_relation.model
end

#migration_name=(class_name) ⇒ Object Also known as: name=



72
73
74
75
# File 'lib/online_migrations/background_migrations/migration.rb', line 72

def migration_name=(class_name)
  class_name = class_name.name if class_name.is_a?(Class)
  write_attribute(:migration_name, self.class.normalize_migration_name(class_name))
end

#migration_objectObject



156
157
158
# File 'lib/online_migrations/background_migrations/migration.rb', line 156

def migration_object
  @migration_object ||= migration_class.new(*arguments)
end

#migration_relationObject



160
161
162
# File 'lib/online_migrations/background_migrations/migration.rb', line 160

def migration_relation
  migration_object.relation
end

#next_batch_rangeObject



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/online_migrations/background_migrations/migration.rb', line 232

def next_batch_range
  iterator = BatchIterator.new(migration_relation)
  batch_range = nil

  on_shard do
    # rubocop:disable Lint/UnreachableLoop
    iterator.each_batch(of: batch_size, column: batch_column_name, start: next_min_value, finish: max_value) do |_relation, min_value, max_value|
      batch_range = [min_value, max_value]

      break
    end
    # rubocop:enable Lint/UnreachableLoop
  end

  return if batch_range.nil?

  min_value, max_value = batch_range
  return if min_value > self.max_value

  max_value = [max_value, self.max_value].min

  [min_value, max_value]
end

#on_shard(&block) ⇒ Object



216
217
218
219
220
221
# File 'lib/online_migrations/background_migrations/migration.rb', line 216

def on_shard(&block)
  abstract_class = Utils.find_connection_class(migration_model)

  shard = (self.shard || abstract_class.default_shard).to_sym
  abstract_class.connected_to(shard: shard, role: :writing, &block)
end

#paused!Object

Overwrite enum’s generated method to correctly work for composite migrations.



83
84
85
86
87
88
89
90
# File 'lib/online_migrations/background_migrations/migration.rb', line 83

def paused!
  return super if !composite?

  transaction do
    super
    children.each { |child| child.paused! if child.enqueued? || child.running? }
  end
end

#progressFloat?

Returns the progress of the background migration.

Returns:

  • (Float, nil)
    • when background migration is configured to not track progress, returns ‘nil`

    • otherwise returns value in range from 0.0 to 100.0



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/online_migrations/background_migrations/migration.rb', line 123

def progress
  if succeeded?
    100.0
  elsif enqueued?
    0.0
  elsif composite?
    rows_counts = children.to_a.pluck(:rows_count)
    if rows_counts.none?(nil)
      total_rows_count = rows_counts.sum
      return 100.0 if total_rows_count == 0

      progresses = children.map do |child|
        child.progress * child.rows_count / total_rows_count # weighted progress
      end

      progresses.sum.round(2)
    end
  elsif rows_count
    if rows_count > 0 && rows_count > batch_size
      jobs_rows_count = migration_jobs.succeeded.sum(:batch_size)
      # The last migration job may need to process the amount of rows
      # less than the batch size, so we can get a value > 1.0.
      ([jobs_rows_count.to_f / rows_count, 1.0].min * 100).round(2)
    else
      0.0
    end
  end
end

#reset_failed_jobs_attemptsObject



224
225
226
227
228
229
# File 'lib/online_migrations/background_migrations/migration.rb', line 224

def reset_failed_jobs_attempts
  iterator = BatchIterator.new(migration_jobs.failed.attempts_exceeded)
  iterator.each_batch(of: 100) do |relation|
    relation.update_all(attempts: 0)
  end
end

#retryObject Also known as: retry_failed_jobs

Mark this migration as ready to be processed again.

This method marks failed jobs as ready to be processed again, and they will be picked up on the next Scheduler run.



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/online_migrations/background_migrations/migration.rb', line 183

def retry
  if composite? && failed?
    children.failed.each(&:retry)
    enqueued!
    true
  elsif failed?
    iterator = BatchIterator.new(migration_jobs.failed)
    iterator.each_batch(of: 100) do |batch|
      batch.each(&:retry)
    end
    enqueued!
    true
  else
    false
  end
end

#running!Object

Overwrite enum’s generated method to correctly work for composite migrations.



93
94
95
96
97
98
99
100
# File 'lib/online_migrations/background_migrations/migration.rb', line 93

def running!
  return super if !composite?

  transaction do
    super
    children.each { |child| child.running! if child.paused? }
  end
end

#started_atObject

Returns the time this migration started running.



202
203
204
205
206
207
208
# File 'lib/online_migrations/background_migrations/migration.rb', line 202

def started_at
  # To be precise, we should get the minimum of `started_at` amongst the children jobs
  # (for simple migrations) and amongst the children migrations (for composite migrations).
  # But we do not have an appropriate index on the jobs table and using this will lead to
  # N+1 queries if used inside some dashboard, for example.
  created_at
end