Backoff jitter strategies based on Marc Brooker's "Exponential Backoff and Jitter" (AWS Architecture Blog, 2015).
Summary
Functions
Applies Equal Jitter to an existing delay stream.
Applies Full Jitter to an existing delay stream.
Returns capped exponential delay with decorrelated jitter.
Returns an infinite lazy stream of decorrelated jitter delays.
Returns capped exponential delay with equal jitter.
Returns an infinite lazy stream of equal jitter delays.
Returns capped exponential delay with full jitter.
Returns an infinite lazy stream of full jitter delays.
Returns capped exponential delay without jitter.
Functions
@spec apply_equal( Enumerable.t(), keyword() ) :: Enumerable.t()
Applies Equal Jitter to an existing delay stream.
Each delay in the input enumerable is capped at :cap and then split in half:
the result is the half plus a uniform random value in [0, half]. Compatible
with Retry.DelayStreams.exponential_backoff/0:
import Retry.DelayStreams
exponential_backoff() |> Jitter.apply_equal(cap: 30_000) |> Stream.take(5)Examples
iex> [100, 200, 400, 800, 2000]
...> |> Jitter.apply_equal(cap: 1000, rng: fn _min, max -> max end)
...> |> Enum.to_list()
[100, 200, 400, 800, 1000]
iex> [100, 200, 400, 800, 2000]
...> |> Jitter.apply_equal(cap: 1000, rng: fn min, _max -> min end)
...> |> Enum.to_list()
[50, 100, 200, 400, 500]
@spec apply_full( Enumerable.t(), keyword() ) :: Enumerable.t()
Applies Full Jitter to an existing delay stream.
Each delay in the input enumerable is capped at :cap and then randomized
uniformly in [0, capped]. Compatible with Retry.DelayStreams.exponential_backoff/0:
import Retry.DelayStreams
exponential_backoff() |> Jitter.apply_full(cap: 30_000) |> Stream.take(5)Examples
iex> [100, 200, 400, 800, 2000]
...> |> Jitter.apply_full(cap: 1000, rng: fn _min, max -> max end)
...> |> Enum.to_list()
[100, 200, 400, 800, 1000]
@spec equal( non_neg_integer(), keyword() ) :: pos_integer()
Returns capped exponential delay with equal jitter.
Examples
iex> Jitter.equal(0, base: 100, cap: 30_000, rng: fn _min, max -> max end)
100
iex> Jitter.equal(3, base: 100, cap: 30_000, rng: fn _min, max -> max end)
800
iex> Jitter.equal(20, base: 100, cap: 1000, rng: fn _min, max -> max end)
1000
@spec equal_stream(keyword()) :: Enumerable.t()
Returns an infinite lazy stream of equal jitter delays.
Each element is calculated from the next retry attempt. The delay is always between half of the capped exponential delay and the full capped exponential delay.
Use Enum.take/2 or Stream.take/2 to consume a finite number of values.
Examples
iex> Jitter.equal_stream(base: 100, cap: 1000, rng: fn _min, max -> max end) |> Enum.take(5)
[100, 200, 400, 800, 1000]
iex> Jitter.equal_stream(base: 100, cap: 1000, rng: fn min, _max -> min end) |> Enum.take(5)
[50, 100, 200, 400, 500]
@spec full( non_neg_integer(), keyword() ) :: pos_integer()
Returns capped exponential delay with full jitter.
Examples
iex> Jitter.full(0, base: 100, cap: 30_000, rng: fn _min, max -> max end)
100
iex> Jitter.full(3, base: 100, cap: 30_000, rng: fn _min, max -> max end)
800
iex> Jitter.full(20, base: 100, cap: 1000, rng: fn _min, max -> max end)
1000
@spec full_stream(keyword()) :: Enumerable.t()
Returns an infinite lazy stream of full jitter delays.
Each element is calculated from the next retry attempt:
attempt 0, attempt 1, attempt 2, ...Use Enum.take/2 or Stream.take/2 to consume a finite number of values.
Examples
iex> Jitter.full_stream(base: 100, cap: 1000, rng: fn _min, max -> max end) |> Enum.take(5)
[100, 200, 400, 800, 1000]
@spec no_jitter( non_neg_integer(), keyword() ) :: pos_integer()
Returns capped exponential delay without jitter.
This is the baseline — no randomness, just exponential growth.
Use this only as a reference; in production prefer full/2 or decorrelated/2.
Examples
iex> Jitter.no_jitter(0, base: 100, cap: 30_000)
100
iex> Jitter.no_jitter(3, base: 100, cap: 30_000)
800
iex> Jitter.no_jitter(20, base: 100, cap: 1000)
1000