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.



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

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



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

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.



279
280
281
282
283
284
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 279

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.



138
139
140
141
142
143
144
145
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 138

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)


255
256
257
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 255

def pausable?
  true
end

#pauseBoolean

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

Returns:

  • (Boolean)

    whether this data migration was paused.



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

def pause
  return false if completed?

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

  true
end

#persist_error(error, attempt) ⇒ Object



238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 238

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



229
230
231
232
233
234
235
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 229

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



265
266
267
268
269
270
271
272
273
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 265

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.



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

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.



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 291

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
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 124

def start
  if enqueued?
    update!(status: :running, started_at: Time.current)
    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



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 195

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