Resiliency.BackoffRetry.Backoff (Resiliency v0.6.0)

Copy Markdown View Source

Stream-based backoff strategies for Resiliency.BackoffRetry.

Each strategy returns an infinite Stream of delay values in milliseconds. Strategies compose naturally via pipes:

Resiliency.BackoffRetry.Backoff.exponential(base: 200)
|> Resiliency.BackoffRetry.Backoff.jitter(0.25)
|> Resiliency.BackoffRetry.Backoff.cap(10_000)

How it works

Every strategy is implemented with Stream.unfold/2 or Stream.repeatedly/1, producing an infinite lazy enumerable. Because streams are lazy, no delays are computed until consumed — typically by Enum.take/2 inside Resiliency.BackoffRetry.retry/2.

Exponential — Each delay is the previous delay multiplied by a growth factor (:multiplier, default 2). The sequence is base, base * m, base * m^2, base * m^3, .... Formally, the k-th delay (0-indexed) is d(k) = base * multiplier^k. This strategy spreads retries further and further apart, which is ideal when the downstream service needs progressively more time to recover.

Linear — Each delay is the previous delay plus a fixed increment. The sequence is base, base + inc, base + 2*inc, .... Formally, d(k) = base + k * increment. Growth is steady and predictable — useful when you want moderate back-pressure without the aggressive growth of exponential.

Constant — Every delay is the same value: delay, delay, delay, .... Formally, d(k) = delay for all k. Best suited for idempotent health-check pings or scenarios where the expected recovery time is roughly fixed.

Jitter — A decorator that adds uniform random noise to each delay value. Given a proportion p, each delay d is replaced by a random value drawn uniformly from [d * (1 - p), d * (1 + p)], floored at 0. Jitter prevents the "thundering herd" problem where many clients retry at exactly the same instant.

Cap — A decorator that clamps each delay to a maximum value: min(d, max_delay). This ensures no single sleep exceeds a practical ceiling regardless of how fast the underlying strategy grows.

Algorithm Complexity

FunctionTimeSpace
exponential/1O(1) to create the stream; O(1) per element consumedO(1) — constant state in the stream accumulator
linear/1O(1) to create the stream; O(1) per element consumedO(1)
constant/1O(1) to create the stream; O(1) per element consumedO(1)
jitter/2O(1) per element consumedO(1)
cap/2O(1) per element consumedO(1)

Summary

Functions

Caps each delay at max_delay milliseconds.

Produces a constant backoff stream: delay, delay, delay, ...

Produces an exponential backoff stream: base, base * mult, base * mult^2, ...

Adds random jitter to each delay value.

Produces a linear backoff stream: base, base + inc, base + 2*inc, ...

Functions

cap(delays, max_delay)

Caps each delay at max_delay milliseconds.

Parameters

  • delays -- an Enumerable of delay values in milliseconds (typically a Stream from another backoff function).
  • max_delay -- the maximum delay in milliseconds. Any delay exceeding this value is clamped to it.

Returns

A Stream of delay values in milliseconds, each capped at max_delay.

Examples

iex> Resiliency.BackoffRetry.Backoff.exponential(base: 1000) |> Resiliency.BackoffRetry.Backoff.cap(5000) |> Enum.take(5)
[1000, 2000, 4000, 5000, 5000]

constant(opts \\ [])

@spec constant(keyword()) :: Enumerable.t()

Produces a constant backoff stream: delay, delay, delay, ...

Parameters

  • opts -- keyword list of options. Defaults to [].
    • :delay -- fixed delay in milliseconds. Defaults to 100.

Returns

An infinite Stream that repeatedly emits the same delay value in milliseconds.

Examples

iex> Resiliency.BackoffRetry.Backoff.constant() |> Enum.take(3)
[100, 100, 100]

iex> Resiliency.BackoffRetry.Backoff.constant(delay: 500) |> Enum.take(3)
[500, 500, 500]

exponential(opts \\ [])

@spec exponential(keyword()) :: Enumerable.t()

Produces an exponential backoff stream: base, base * mult, base * mult^2, ...

Parameters

  • opts -- keyword list of options. Defaults to [].
    • :base -- initial delay in milliseconds. Defaults to 100.
    • :multiplier -- growth factor applied each iteration. Defaults to 2.

Returns

An infinite Stream of delay values in milliseconds, growing exponentially.

Examples

iex> Resiliency.BackoffRetry.Backoff.exponential() |> Enum.take(4)
[100, 200, 400, 800]

iex> Resiliency.BackoffRetry.Backoff.exponential(base: 50, multiplier: 3) |> Enum.take(3)
[50, 150, 450]

jitter(delays, proportion \\ 0.25)

@spec jitter(Enumerable.t(), float()) :: Enumerable.t()

Adds random jitter to each delay value.

Each delay d becomes a random value in [d * (1 - proportion), d * (1 + proportion)], floored at 0.

Parameters

  • delays -- an Enumerable of delay values in milliseconds (typically a Stream from another backoff function).
  • proportion -- the jitter fraction, controlling how much each delay can vary. Defaults to 0.25.

Returns

A Stream of jittered delay values in milliseconds.

Examples

Resiliency.BackoffRetry.Backoff.exponential()
|> Resiliency.BackoffRetry.Backoff.jitter(0.25)

linear(opts \\ [])

@spec linear(keyword()) :: Enumerable.t()

Produces a linear backoff stream: base, base + inc, base + 2*inc, ...

Parameters

  • opts -- keyword list of options. Defaults to [].
    • :base -- initial delay in milliseconds. Defaults to 100.
    • :increment -- additive step per retry in milliseconds. Defaults to 100.

Returns

An infinite Stream of delay values in milliseconds, growing linearly.

Examples

iex> Resiliency.BackoffRetry.Backoff.linear() |> Enum.take(4)
[100, 200, 300, 400]

iex> Resiliency.BackoffRetry.Backoff.linear(base: 50, increment: 50) |> Enum.take(3)
[50, 100, 150]