Class: Tenax::Retry::Backoff::DecorrelatedJitter

Inherits:
Strategy
  • Object
show all
Defined in:
lib/tenax/retry/backoff/decorrelated_jitter.rb

Overview

Decorrelated jitter backoff (AWS-style).

delay = min(cap, random(base, previous_delay * 3)) On the first attempt (no previous_delay), delay = random(base, base * 3).

This is the recommended backoff for production retry against shared remote services. It avoids both the synchronization problem of plain exponential backoff and the "stuck low" problem of full jitter.

See: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/

Examples:

backoff = Tenax::Retry::Backoff::DecorrelatedJitter.new(base: 0.2, cap: 10)
d1 = backoff.delay_for(1)                              # => random(0.2, 0.6)
d2 = backoff.delay_for(2, previous_delay: d1)          # => random(0.2, d1 * 3)
d3 = backoff.delay_for(3, previous_delay: d2)          # => random(0.2, d2 * 3)

Instance Method Summary collapse

Constructor Details

#initialize(base:, cap:, random: Random.new) ⇒ DecorrelatedJitter

Returns a new instance of DecorrelatedJitter.

Parameters:

  • base (Numeric) —

    minimum/seed delay in seconds; positive

  • cap (Numeric) —

    maximum delay in seconds; >= base

  • random (#rand) (defaults to: Random.new) —

    random source (injectable for testing)

Raises:

  • (ArgumentError) —

    on invalid configuration



30
31
32
33
34
35
36
37
38
39
# File 'lib/tenax/retry/backoff/decorrelated_jitter.rb', line 30

def initialize(base:, cap:, random: Random.new)
  raise ArgumentError, "base must be positive, got #{base}" if base <= 0
  raise ArgumentError, "cap must be >= base, got base=#{base}, cap=#{cap}" if cap < base

  super()
  @base   = base.to_f
  @cap    = cap.to_f
  @random = random
  freeze
end

Instance Method Details

#delay_for(attempt, previous_delay: nil) ⇒ Float

Returns delay seconds in [base, cap].

Parameters:

  • attempt (Integer) —

    1-based attempt number

  • previous_delay (Float, nil) (defaults to: nil) —

    delay used before the previous attempt

Returns:

  • (Float) —

    delay seconds in [base, cap]

Raises:

  • (ArgumentError)


44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/tenax/retry/backoff/decorrelated_jitter.rb', line 44

def delay_for(attempt, previous_delay: nil)
  raise ArgumentError, "attempt must be >= 1, got #{attempt}" if attempt < 1

  # On the first retry, seed with `base` so the formula has a starting point.
  prev = previous_delay || @base
  upper = [prev * 3, @cap].min

  # If upper has fallen to base (or below — shouldn't happen but be defensive),
  # we just return base; no random range to draw from.
  return @base if upper <= @base

  @base + (@random.rand * (upper - @base))
end