Class: OnlineMigrations::BackgroundDataMigrations::Migration

Inherits:
ApplicationRecord
  • Object
show all
Includes:
ShardAware
Defined in:
lib/online_migrations/background_data_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 =
[
  "pending",     # The migration has been created by the user.
  "enqueued",    # The migration has been enqueued by the scheduler.
  "running",     # The migration is being performed by a migration executor.
  "pausing",     # The migration has been told to pause but is finishing work.
  "paused",      # The migration was paused in the middle of the run by the user.
  "errored",     # The migration raised an error during last run.
  "failed",      # The migration raises an error when running and retry attempts exceeded.
  "succeeded",   # The migration finished without error.
  "cancelling",  # The migration has been told to cancel but is finishing work.
  "cancelled",   # The migration was cancelled by the user.
  "delayed",     # The migration was created, but waiting approval from the user to start running.
]
COMPLETED_STATUSES =
["succeeded", "failed", "cancelled"]
ACTIVE_STATUSES =
[
  "pending",
  "enqueued",
  "running",
  "failed",
  "pausing",
  "paused",
  "cancelling",
]
STOPPING_STATUSES =
["pausing", "cancelling", "cancelled"]

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ShardAware

#connection_class, #connection_class_name=, #on_shard_if_present

Class Method Details

.normalize_migration_name(migration_name) ⇒ Object



66
67
68
69
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 66

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

Instance Method Details

#active?Boolean

Returns whether the migration is active, which is defined as having a status of pending, enqueued, running, pausing, paused, or cancelling.

Returns:

  • (Boolean)

    whether the migration is active.



107
108
109
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 107

def active?
  ACTIVE_STATUSES.include?(status)
end

#cancelBoolean

Cancel this data migration. No-op if migration is completed.

Returns:

  • (Boolean)

    whether this data migration was cancelled.



166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 166

def cancel
  return false if completed?

  if paused? || delayed? || stuck?
    update!(status: :cancelled, finished_at: Time.current)
  elsif pending? || enqueued? || errored?
    cancelled!
  else
    cancelling!
  end

  true
end

#completeObject



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 227

def complete
  return false if completed?

  if running?
    update!(status: :succeeded, finished_at: Time.current)
    data_migration.after_complete
  elsif pausing?
    update!(status: :paused, finished_at: Time.current)
  elsif cancelling?
    update!(status: :cancelled, finished_at: Time.current)
    data_migration.after_complete
  end

  true
end

#completed?Boolean

Returns whether the migration is completed, which is defined as having a status of succeeded, failed, or cancelled.

Returns:

  • (Boolean)

    whether the migration is completed.



98
99
100
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 98

def completed?
  COMPLETED_STATUSES.include?(status)
end

#cursorObject

TODO: delete in some future version



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

def cursor
  self[:cursor]
rescue JSON::ParserError
  cursor_before_type_cast
end

#data_migrationOnlineMigrations::DataMigration

Returns data migration associated with this migration.



294
295
296
297
298
299
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 294

def data_migration
  @data_migration ||= begin
    klass = DataMigration.named(migration_name)
    klass.new(*arguments)
  end
end

#enqueueBoolean

Enqueue this data migration. No-op if migration is not delayed.

Returns:

  • (Boolean)

    whether this data migration was enqueued.



153
154
155
156
157
158
159
160
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 153

def enqueue
  if delayed?
    pending!
    true
  else
    false
  end
end

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



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

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

#pausable?Boolean

Returns whether this migration is pausable.

Returns:

  • (Boolean)


270
271
272
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 270

def pausable?
  true
end

#pauseBoolean

Pause this data migration. No-op if migration is completed.

Returns:

  • (Boolean)

    whether this data migration was paused.



184
185
186
187
188
189
190
191
192
193
194
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 184

def pause
  return false if completed?

  if pending? || enqueued? || delayed? || stuck? || errored?
    paused!
  else
    pausing!
  end

  true
end

#persist_error(error, attempt) ⇒ Object



253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 253

def persist_error(error, attempt)
  backtrace = error.backtrace
  backtrace_cleaner = OnlineMigrations.config.backtrace_cleaner
  backtrace = backtrace_cleaner.clean(backtrace) if backtrace_cleaner
  status = attempt >= max_attempts ? :failed : :errored

  update!(
    status: status,
    finished_at: Time.current,
    error_class: error.class.name,
    error_message: error.message,
    backtrace: backtrace
  )
end

#persist_progress(cursor, number_of_ticks, duration) ⇒ Object



244
245
246
247
248
249
250
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 244

def persist_progress(cursor, number_of_ticks, duration)
  update!(
    cursor: cursor,
    tick_count: tick_count + number_of_ticks,
    time_running: time_running + duration
  )
end

#progressFloat?

Returns the progress of the data 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


280
281
282
283
284
285
286
287
288
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 280

def progress
  if succeeded?
    100.0
  elsif tick_total == 0
    0.0
  elsif tick_total
    ([tick_count.to_f / tick_total, 1.0].min * 100)
  end
end

#resumeBoolean

Resume this data migration. No-op if migration is not paused.

Returns:

  • (Boolean)

    whether this data migration was resumed.



200
201
202
203
204
205
206
207
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 200

def resume
  if paused?
    pending!
    true
  else
    false
  end
end

#retryObject

Mark this migration as ready to be processed again.

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



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 306

def retry
  if failed?
    update!(
      status: :pending,
      started_at: nil,
      finished_at: nil,
      error_class: nil,
      error_message: nil,
      backtrace: nil,
      jid: nil
    )
    true
  else
    false
  end
end

#startObject



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 133

def start
  if enqueued?
    # Defer `tick_total` calculation to the migration starting time
    # instead of the enqueueing time to avoid failed deploys.
    self.tick_total ||= safely_calculate_tick_total
    self.status = :running
    self.started_at = Time.current
    save!

    data_migration.after_start
    true
  else
    false
  end
end

#started?Boolean

Returns whether the migration has been started, which is indicated by the started_at timestamp being present.

Returns:

  • (Boolean)

    whether the migration was started.



89
90
91
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 89

def started?
  started_at.present?
end

#stopObject



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 210

def stop
  return false if completed?

  if cancelling?
    update!(status: :cancelled, finished_at: Time.current)
    data_migration.after_cancel
    data_migration.after_complete
  elsif pausing?
    paused!
    data_migration.after_pause
  end

  data_migration.after_stop
  true
end

#stopping?Boolean

Returns whether the migration is stopping, which is defined as having a status of pausing or cancelling. The status of cancelled is also considered stopping since a migration can be cancelled while its job still exists in the queue, and we want to handle it the same way as a cancelling run.

Returns:

  • (Boolean)

    whether the migration is stopping.



118
119
120
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 118

def stopping?
  STOPPING_STATUSES.include?(status)
end

#stuck?Boolean

Returns whether a migration is stuck, which is defined as having a status of cancelling or pausing, and not having been updated in the last 5 minutes.

Returns:

  • (Boolean)

    whether the migration is stuck.



127
128
129
130
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 127

def stuck?
  stuck_timeout = OnlineMigrations.config.background_data_migrations.stuck_timeout
  (cancelling? || pausing?) && updated_at <= stuck_timeout.ago
end