policybook / retry / decorrelated-jitter

Decorrelated jitter

A random walk that grows from the last delay rather than the attempt number. The only stateful policy here, and the one that disperses a fleet most thoroughly.

Every other policy in this domain computes its delay from the attempt number, so a client’s whole schedule is fixed the moment it starts failing. This one is a random walk: each delay is drawn from a range reaching up to three times the previous delay, and the sequence wanders upward rather than climbing a fixed ladder.

delay = min(cap, base + rng.nextInt(prev * 3 - base + 1))
prev  = delay

On the canonical workload it records the lowest peak simultaneous retries of any policy here: 1.1%, against full jitter’s 2.6% and plain exponential’s 15.1%. Because whole schedules diverge rather than individual attempts, two clients that started together are at very different points a few attempts in.

When to use it

  • When dispersing a large fleet matters most. This is the strongest decorrelation on offer, and the metric says so.
  • Behind a dependency that many clients share. The longer the outage runs, the further apart the walks drift, so the pressure keeps spreading rather than settling into a periodic pattern.
  • When you can tolerate an unpredictable schedule. A client might reach the cap in six attempts or eighty-seven. If nothing downstream depends on the timing, that spread is pure benefit.
  • When per-client state is acceptable. One integer, but it must live as long as the retry sequence.

When not to use it

  • When the delay sequence must be predictable. This is the only policy here whose schedule you cannot state in advance, even in distribution over a fixed attempt count. Debugging a slow retry is correspondingly harder.
  • When a policy object cannot be kept alive across attempts. The walk lives in prev. A policy reconstructed per attempt restarts at base every time and degenerates into a fixed-range draw. If your retry loop builds the policy inside it, use full jitter.
  • When you want the shortest time to success. It has the longest mean time to success of the jittered policies on the benchmark, because it climbs slowly on average. See below.
  • As a default. full jitter is simpler, stateless, and close enough on the herd metric for most fleets.

How it works

One thing here is easy to get wrong, so it comes first.

It climbs more slowly than doubling, despite reaching for three times the last delay. The range looks aggressive, and the natural reading is that this policy backs off faster than exponential. It does not. The draw is uniform over [base, 3 x prev], so the expected step is (base + 3 x prev) / 2: about 1.5x, measured at 1.53 over a long run, against exponential’s exact 2x.

The consequence is measurable: reaching a 10-second cap from a 100 ms base takes a median of 14 attempts, where exponential reaches it at attempt 8 every time. This page originally said the opposite, and a test caught it. The test now records the real figures.

The variance is the point, not the speed. Those 14 attempts range from 6 to 87 across clients. Exponential’s 8 is 8 for everyone.

prev starts at base, which is where the walk begins, and the span 3 x prev - base is therefore always at least 2 x base: positive for any valid configuration, so the draw never has an empty range.

The walk advances to the delay actually used, cap included. Advancing to the uncapped draw instead would leave a client at the ceiling drawing from an ever-growing range it can never reach: the delay would be pinned but the distribution would keep climbing, which is meaningless state. A vector pins the capped behaviour.

The attempt number is ignored except for the give-up check. Asking twice with the same attempt number gives two different answers that both advance the walk. A vector pins that too, and it is what distinguishes this policy from every other one here.

Parameters

Complexity

O(1) time: no loop at all, unlike the policies that recompute an exponential ceiling. O(1) space: a single integer of state.

Source

Marc Brooker, Exponential Backoff And Jitter, AWS Architecture Blog, 2015, where it appears as sleep = min(cap, random_between(base, sleep * 3)). That write-up found it competitive with full jitter on completion time while doing less total work.

Its neighbours are full jitter, which spreads each attempt independently from a fixed ceiling, and equal jitter, which spreads half of one. This spreads the whole schedule, which is why it wins the herd metric and loses the latency one.

Notes

No patents known.

The Rng is supplied at construction rather than passed to nextDelay (see the domain interface for why). For this policy that matters more than for the others: the object holds the walk, so its lifetime is the retry sequence.

Parameters

Name Type Default Description
baseMs number 100 The floor of every delay, and where the walk starts.
capMs number 10000 No delay exceeds this, however far the walk has climbed.
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/