Class: OnlineMigrations::LockRetrier

Inherits:
Object
  • Object
show all
Defined in:
lib/online_migrations/lock_retrier.rb

Overview

This class provides a way to automatically retry code that relies on acquiring a database lock in a way designed to minimize impact on a busy production database.

This class defines an interface for child classes to implement to configure timing configurations and the maximum number of attempts.

There are two predefined implementations (see OnlineMigrations::ConstantLockRetrier and OnlineMigrations::ExponentialLockRetrier). It is easy to provide more sophisticated implementations.

Examples:

Custom LockRetrier implementation

module OnlineMigrations
  class SophisticatedLockRetrier < LockRetrier
    TIMINGS = [
      [0.1.seconds, 0.05.seconds], # first - lock timeout, second - delay time
      [0.1.seconds, 0.05.seconds],
      [0.2.seconds, 0.05.seconds],
      [0.3.seconds, 0.10.seconds],
      [1.second, 5.seconds],
      [1.second, 1.minute],
      [0.1.seconds, 0.05.seconds],
      [0.2.seconds, 0.15.seconds],
      [0.5.seconds, 2.seconds],
      [0.5.seconds, 2.seconds],
      [3.seconds, 3.minutes],
      [0.1.seconds, 0.05.seconds],
      [0.5.seconds, 2.seconds],
      [5.seconds, 2.minutes],
      [7.seconds, 5.minutes],
      [0.5.seconds, 2.seconds],
    ]

    def attempts
      TIMINGS.size
    end

    def lock_timeout(attempt)
      TIMINGS[attempt - 1][0]
    end

    def delay(attempt)
      TIMINGS[attempt - 1][1]
    end
  end

Direct Known Subclasses

ConstantLockRetrier, ExponentialLockRetrier

Instance Method Summary collapse

Instance Method Details

#attemptsObject

Returns the number of retrying attempts

Raises:

  • (NotImplementedError)


51
52
53
# File 'lib/online_migrations/lock_retrier.rb', line 51

def attempts
  raise NotImplementedError
end

#delay(_attempt) ⇒ Object

Returns sleep time after unsuccessful lock attempt (in seconds)

Parameters:

  • _attempt (Integer)

    attempt number

Raises:

  • (NotImplementedError)


65
66
67
# File 'lib/online_migrations/lock_retrier.rb', line 65

def delay(_attempt)
  raise NotImplementedError
end

#lock_timeout(_attempt) ⇒ Object

Returns database lock timeout value (in seconds) for specified attempt number

Parameters:

  • _attempt (Integer)

    attempt number



59
# File 'lib/online_migrations/lock_retrier.rb', line 59

def lock_timeout(_attempt); end

#with_lock_retries(connection, &block) ⇒ void

This method returns an undefined value.

Executes the block with a retry mechanism that alters the lock_timeout and sleep time between attempts.

Examples:

retrier.with_lock_retries(connection) do
  add_column(:users, :name, :string)
end

Parameters:

  • connection

    The connection on which to retry lock timeouts



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/online_migrations/lock_retrier.rb', line 80

def with_lock_retries(connection, &block)
  return yield if lock_retries_disabled?

  current_attempt = 0

  begin
    current_attempt += 1

    current_lock_timeout = lock_timeout(current_attempt)
    if current_lock_timeout
      with_lock_timeout(connection, current_lock_timeout.in_milliseconds, &block)
    else
      yield
    end
  rescue ActiveRecord::LockWaitTimeout
    if current_attempt <= attempts
      current_delay = delay(current_attempt)
      Utils.say("Lock timeout. Retrying in #{current_delay} seconds...")
      sleep(current_delay)
      retry
    end
    raise
  end
end