class OnlineMigrations::BackgroundMigrations::Migration

Constants

STATUSES

Public Class Methods

normalize_migration_name(migration_name) click to toggle source

@private

# File lib/online_migrations/background_migrations/migration.rb, line 48
def self.normalize_migration_name(migration_name)
  namespace = ::OnlineMigrations.config.background_migrations.migrations_module
  migration_name.sub(/^(::)?#{namespace}::/, "")
end

Public Instance Methods

completed?() click to toggle source
# File lib/online_migrations/background_migrations/migration.rb, line 57
def completed?
  succeeded? || failed?
end
interval_elapsed?() click to toggle source

Returns whether the interval between previous step run has passed. @return [Boolean]

# File lib/online_migrations/background_migrations/migration.rb, line 101
def interval_elapsed?
  if migration_jobs.running.exists?
    false
  elsif (job = last_completed_job)
    job.finished_at + batch_pause <= Time.current
  else
    true
  end
end
last_completed_job() click to toggle source
# File lib/online_migrations/background_migrations/migration.rb, line 65
def last_completed_job
  migration_jobs.completed.order(finished_at: :desc).first
end
last_job() click to toggle source
# File lib/online_migrations/background_migrations/migration.rb, line 61
def last_job
  migration_jobs.order(max_value: :desc).first
end
migration_class() click to toggle source
# File lib/online_migrations/background_migrations/migration.rb, line 86
def migration_class
  BackgroundMigration.named(migration_name)
end
migration_name=(class_name) click to toggle source
# File lib/online_migrations/background_migrations/migration.rb, line 53
def migration_name=(class_name)
  write_attribute(:migration_name, self.class.normalize_migration_name(class_name))
end
migration_object() click to toggle source
# File lib/online_migrations/background_migrations/migration.rb, line 90
def migration_object
  @migration_object ||= migration_class.new(*arguments)
end
migration_relation() click to toggle source
# File lib/online_migrations/background_migrations/migration.rb, line 94
def migration_relation
  migration_object.relation
end
next_batch_range() click to toggle source

@private

# File lib/online_migrations/background_migrations/migration.rb, line 127
def next_batch_range
  iterator = BatchIterator.new(migration_relation)
  batch_range = nil

  # rubocop:disable Lint/UnreachableLoop
  iterator.each_batch(of: batch_size, column: batch_column_name, start: next_min_value) do |relation|
    if Utils.ar_version <= 4.2
      # ActiveRecord <= 4.2 does not support pluck with Arel nodes
      quoted_column = self.class.connection.quote_column_name(batch_column_name)
      batch_range = relation.pluck("MIN(#{quoted_column}), MAX(#{quoted_column})").first
    else
      min = relation.arel_table[batch_column_name].minimum
      max = relation.arel_table[batch_column_name].maximum

      batch_range = relation.pluck(min, max).first
    end
    break
  end
  # rubocop:enable Lint/UnreachableLoop

  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
progress() click to toggle source

Returns the progress of the background migration.

@return [Float, nil]

- when background migration is configured to not to track progress, returns `nil`
- otherwise returns value in range of 0.0 and 1.0
# File lib/online_migrations/background_migrations/migration.rb, line 75
def progress
  if succeeded?
    1.0
  elsif rows_count
    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
  end
end
retry_failed_jobs() click to toggle source

Manually retry failed jobs.

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

# File lib/online_migrations/background_migrations/migration.rb, line 116
def retry_failed_jobs
  iterator = BatchIterator.new(migration_jobs.failed)
  iterator.each_batch(of: 100) do |batch|
    transaction do
      batch.each(&:retry)
      enqueued!
    end
  end
end

Private Instance Methods

next_min_value() click to toggle source
# File lib/online_migrations/background_migrations/migration.rb, line 202
def next_min_value
  if last_job
    last_job.max_value.next
  else
    min_value
  end
end
set_defaults() click to toggle source
# File lib/online_migrations/background_migrations/migration.rb, line 178
def set_defaults
  if migration_relation.is_a?(ActiveRecord::Relation)
    self.batch_column_name  ||= migration_relation.primary_key
    self.min_value          ||= migration_relation.minimum(batch_column_name)
    self.max_value          ||= migration_relation.maximum(batch_column_name)

    count = migration_object.count
    self.rows_count = count if count != :no_count
  end

  config = ::OnlineMigrations.config.background_migrations
  self.batch_size           ||= config.batch_size
  self.sub_batch_size       ||= config.sub_batch_size
  self.batch_pause          ||= config.batch_pause
  self.sub_batch_pause_ms   ||= config.sub_batch_pause_ms
  self.batch_max_attempts   ||= config.batch_max_attempts

  # This can be the case when run in development on empty tables
  if min_value.nil?
    # integer IDs minimum value is 1
    self.min_value = self.max_value = 1
  end
end
validate_batch_column_values() click to toggle source
# File lib/online_migrations/background_migrations/migration.rb, line 158
def validate_batch_column_values
  if max_value.to_i < min_value.to_i
    errors.add(:base, "max_value should be greater than or equal to min_value")
  end
end
validate_batch_sizes() click to toggle source
# File lib/online_migrations/background_migrations/migration.rb, line 164
def validate_batch_sizes
  if sub_batch_size.to_i > batch_size.to_i
    errors.add(:base, "sub_batch_size should be smaller than or equal to batch_size")
  end
end
validate_jobs_status() click to toggle source
# File lib/online_migrations/background_migrations/migration.rb, line 170
def validate_jobs_status
  if succeeded? && migration_jobs.except_succeeded.exists?
    errors.add(:base, "all migration jobs must be succeeded")
  elsif failed? && !migration_jobs.failed.exists?
    errors.add(:base, "at least one migration job must be failed")
  end
end