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 Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#connectionObject

Database connection on which retries are run



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

def connection
  @connection
end

Instance Method Details

#attemptsObject

Returns the number of retrying attempts

Raises:

  • (NotImplementedError)


55
56
57
# File 'lib/online_migrations/lock_retrier.rb', line 55

def attempts
  raise NotImplementedError
end

#delay(_attempt) ⇒ Object

Returns sleep time after unsuccessful lock attempt (in seconds)

Parameters:

  • _attempt (Integer)

    attempt number

Raises:

  • (NotImplementedError)


69
70
71
# File 'lib/online_migrations/lock_retrier.rb', line 69

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



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

def lock_timeout(_attempt); end

#with_lock_retries(&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 do
  add_column(:users, :name, :string)
end


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

def with_lock_retries(&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(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