Jitter (Jitter v0.1.0)

Copy Markdown View Source

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

apply_equal(delays, opts)

@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]

apply_full(delays, opts)

@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]

decorrelated(prev_delay, opts)

@spec decorrelated(
  pos_integer(),
  keyword()
) :: pos_integer()

Returns capped exponential delay with decorrelated jitter.

Examples

iex> Jitter.decorrelated(100, base: 100, cap: 30_000, rng: fn _min, max -> max end)
300

iex> Jitter.decorrelated(200, base: 100, cap: 30_000, rng: fn min, _max -> min end)
100

iex> Jitter.decorrelated(20_000, base: 100, cap: 30_000, rng: fn _min, max -> max end)
30000

decorrelated_stream(opts \\ [])

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

Returns an infinite lazy stream of decorrelated jitter delays.

Unlike full_stream/1 and equal_stream/1, this stream keeps the previous delay as internal state. Each generated delay becomes the previous delay for the next step.

Use Enum.take/2 or Stream.take/2 to consume a finite number of values.

Examples

iex> Jitter.decorrelated_stream(base: 100, cap: 1000, rng: fn _min, max -> max end) |> Enum.take(5)
[300, 900, 1000, 1000, 1000]

equal(attempt, opts)

@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

equal_stream(opts \\ [])

@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]

full(attempt, opts)

@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

full_stream(opts \\ [])

@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]

no_jitter(attempt, opts)

@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