policybook / retry / equal-jitter

Equal jitter

Half the exponential delay fixed, half of it random. Guarantees a floor that grows with the attempt, where full jitter can return zero every time.

Take the exponential ceiling, keep half of it as a floor, and draw the rest:

half  = min(cap, base * 2^(attempt-1)) / 2
delay = half + rng.nextInt(half + 1)

Every delay lands in [half, 2 x half] and averages about three quarters of the un-jittered ceiling, against full jitter’s one half and exponential’s whole.

On the canonical workload it sits exactly where that arithmetic predicts: 30.2% success against exponential’s 40.0% and full jitter’s 19.9%, with a herd of 3.1% against 15.1% and 2.6%. It buys back most of exponential’s success rate while keeping nearly all of full jitter’s spreading.

When to use it

  • When the backoff must actually back off. Full jitter can return zero on any attempt, so an unlucky client retries immediately eight times running and gives a struggling service no respite. This cannot fall below half the ceiling, ever.
  • In front of something that needs recovery time. A database replaying a log, a cache warming, a connection pool refilling: a guaranteed minimum matters more here than the last few percent of dispersal.
  • Against a rate limiter you must not trip again immediately. A zero-length wait after a 429 is wasted work at best.
  • When you want jitter but the zero delays make reviewers nervous. That is a legitimate reason: a policy nobody trusts gets configured around.

When not to use it

  • When maximum dispersal is the goal. Full jitter spreads over twice the range, and decorrelated jitter spreads whole schedules. This one’s clients are all within a factor of two of each other on every attempt.
  • With a very small base. Integer halving means a ceiling of 1 halves to 0 and every delay becomes 0: the policy stops backing off entirely. A vector pins that so nobody discovers it in production, but the real answer is not to configure a 1 ms base.
  • When you have measurements saying full jitter is better. AWS’s 2015 numbers put full jitter slightly ahead on both completion time and total work, and this registry’s benchmark agrees on the herd metric. Equal jitter is the answer when the guaranteed floor is worth those points.

How it works

The halving is integer division, matching the three ports exactly. At a ceiling of 1 the half is 0 and 0 + nextInt(1) is always 0: degenerate, and documented rather than special-cased, because a special case would be a different policy.

Both ends of the range are reachable. half + nextInt(half + 1) produces half at one end and 2 x half at the other. A test checks both occur, since an off-by-one in the bound would silently narrow the range.

Giving up consumes no randomness. The two refusal checks precede the draw, so a declined call leaves the stream where it was. A port that ordered these differently would diverge from the first give-up onward.

The exponential ceiling is restated here rather than imported from exponential, because policybook add copies a policy file whole and a copy that reached back into the registry would not compile in your project. backoff-policies.test.ts pins the copies against each other.

Parameters

Complexity

O(attempt) time: a bounded loop that stops at the cap, so a handful of multiplications. O(1) space.

Source

Marc Brooker, Exponential Backoff And Jitter, AWS Architecture Blog, 2015, where it appears alongside full jitter and decorrelated jitter as sleep = temp/2 + random_between(0, temp/2).

Its neighbours bracket it precisely. exponential is this policy with the random half removed, and full jitter is this policy with the fixed half removed. The benchmark’s success rates of 40.0%, 30.2%, and 19.9% fall in exactly that order, which is a pleasant confirmation that the three are doing what the arithmetic says.

Notes

No patents known.

The Rng is supplied at construction rather than passed to nextDelay (see the domain interface for why).

Parameters

Name Type Default Description
baseMs number 100 The first ceiling, doubled on each subsequent attempt.
capMs number 10000 No ceiling exceeds this, however many attempts have failed.
maxAttempts number 8 Give up after this many attempts.

Source

Exponential Backoff And Jitter by Marc Brooker. AWS Architecture Blog 2015.

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