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



64
65
66
67
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 64

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.



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

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.



157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 157

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



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 218

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.



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

def completed?
  COMPLETED_STATUSES.include?(status)
end

#data_migrationOnlineMigrations::DataMigration

Returns data migration associated with this migration.



285
286
287
288
289
290
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 285

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.



144
145
146
147
148
149
150
151
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 144

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

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



69
70
71
72
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 69

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)


261
262
263
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 261

def pausable?
  true
end

#pauseBoolean

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

Returns:

  • (Boolean)

    whether this data migration was paused.



175
176
177
178
179
180
181
182
183
184
185
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 175

def pause
  return false if completed?

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

  true
end

#persist_error(error, attempt) ⇒ Object



244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 244

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



235
236
237
238
239
240
241
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 235

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


271
272
273
274
275
276
277
278
279
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 271

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.



191
192
193
194
195
196
197
198
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 191

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.



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 297

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



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 124

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.



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

def started?
  started_at.present?
end

#stopObject



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 201

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.



109
110
111
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 109

def stopping?
  STOPPING_STATUSES.include?(status)
end

#stuck?Boolean

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

Returns:

  • (Boolean)

    whether the migration is stuck.



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

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