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 =
[
  "enqueued",    # The migration has been enqueued by the user.
  "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.
  "failed",      # The migration raises an exception when running.
  "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.
]
COMPLETED_STATUSES =
["succeeded", "failed", "cancelled"]
ACTIVE_STATUSES =
[
  "enqueued",
  "running",
  "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



59
60
61
62
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 59

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 enqueued, running, pausing, paused, or cancelling.

Returns:

  • (Boolean)

    whether the migration is active.



93
94
95
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 93

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.



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

def cancel
  return false if completed?

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

  true
end

#completeObject



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

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.



84
85
86
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 84

def completed?
  COMPLETED_STATUSES.include?(status)
end

#data_migrationOnlineMigrations::DataMigration

Returns data migration associated with this migration.



264
265
266
267
268
269
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 264

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

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



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

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)


240
241
242
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 240

def pausable?
  true
end

#pauseBoolean

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

Returns:

  • (Boolean)

    whether this data migration was paused.



155
156
157
158
159
160
161
162
163
164
165
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 155

def pause
  return false if completed?

  if enqueued? || stuck?
    paused!
  else
    pausing!
  end

  true
end

#persist_error(error) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 224

def persist_error(error)
  backtrace = error.backtrace
  backtrace_cleaner = OnlineMigrations.config.backtrace_cleaner
  backtrace = backtrace_cleaner.clean(backtrace) if backtrace_cleaner

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

#persist_progress(cursor, number_of_ticks, duration) ⇒ Object



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

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



250
251
252
253
254
255
256
257
258
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 250

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.



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

def resume
  if paused?
    enqueued!
    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.



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 276

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

#startObject



119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 119

def start
  if running? && !started?
    update!(started_at: Time.current)
    data_migration.after_start
    true
  elsif 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.



75
76
77
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 75

def started?
  started_at.present?
end

#stopObject



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

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.



104
105
106
# File 'lib/online_migrations/background_data_migrations/migration.rb', line 104

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.



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

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