Hermes Wiki
Architecture/Fundamentals/retry-backoff-and-jitter

Retry Strategies: Backoff, Jitter, and Retry Storms

Concept

Retrying a failed request seems like the obvious fix for transient failures — a dropped packet, a momentary overload, a rolling deploy briefly returning 503s. Done naively, though, retries make outages worse instead of better: if every client retries immediately on failure, and the failure was caused by the server being overloaded, an instant retry storm hits the already-struggling server with the same load again, often synchronized across thousands of clients that all failed and all retry at the same instant. This is exactly how a brief blip turns into an extended outage — the retries themselves become the load that prevents recovery.

Exponential backoff is the first fix: instead of retrying immediately, wait progressively longer between attempts (1s, 2s, 4s, 8s...), giving the struggling system room to recover instead of hitting it again at the same rate. But exponential backoff alone doesn't solve the synchronization problem — if 10,000 clients all failed at the same moment (because the server just went down), they all compute the same backoff schedule and all retry at the same moment again, just later. Jitter — adding randomness to the backoff delay — breaks that synchronization by spreading retries across a time window instead of a single instant, so the server sees a smoothed trickle of retries rather than a repeated synchronized spike. The AWS Architecture Blog's canonical formulation is "full jitter": pick the retry delay as a random value between 0 and the exponential backoff ceiling, rather than the ceiling itself, which empirically produces both the lowest total retry count and the fastest recovery time compared to backoff-without-jitter or fixed-interval retry.

The second half of the pattern is bounding it: a maximum retry count (or a maximum total elapsed time budget) so a client doesn't retry forever against a genuinely down dependency, and a circuit breaker sitting above the retry logic so that once failures cross a threshold, the client stops retrying entirely for a cooldown window instead of continuing to add load to a system that's clearly not recovering.

Tradeoffs

Strategy Retry storm risk Recovery speed Complexity
Immediate retry, no backoff Highest — retries synchronized and undamped Can worsen outages Trivial
Exponential backoff, no jitter Moderate — delays grow but stay synchronized across clients that failed together Better than immediate, but thundering-herd risk remains Low
Exponential backoff + full jitter Lowest — retries spread across a window Fastest empirical recovery in AWS's testing Low-moderate
Backoff + jitter + circuit breaker Lowest, plus stops retrying against a confirmed-down dependency Fast recovery, and protects the caller from wasting resources on a hopeless call Moderate — needs failure-rate tracking and state

The tension is between individual-request optimism (retry aggressively, get this specific request through as fast as possible) and system-wide stability (every client retrying aggressively at the same moment is the mechanism that turns a blip into an outage). Backoff and jitter trade a small amount of per-request latency for materially better collective behavior under load — the "cost" is real but small compared to the failure mode it prevents.

When to use / when not to

  • Use retries with backoff and jitter for any call to a dependency that can fail transiently — network calls, downstream service calls, database connections — where a repeat attempt has a real chance of succeeding.
  • Always cap total retry attempts or retry budget; an unbounded retry loop against a truly down dependency just burns resources and adds load without ever succeeding.
  • Pair retries with a circuit breaker for any dependency call in the hot path — once failures cross a threshold, stop retrying and fail fast instead of continuing to hammer a confirmed-down service.
  • Skip retries entirely for non-idempotent operations unless they're also protected by an idempotency key — retrying a POST that already partially succeeded can duplicate the side effect instead of fixing the failure.
  • Skip retries for client errors (4xx) that won't change on a repeat attempt — a malformed request or an auth failure needs a fix, not a retry.

Common pitfall

Implementing exponential backoff without jitter and assuming the problem is solved. It's a common half-measure — backoff clearly helps compared to immediate retry, so it looks fixed — but every client that failed at the same instant (a deploy, a brief network partition, a downstream outage) computes the identical backoff schedule and retries in lockstep at every subsequent interval, reproducing the thundering-herd problem at each backoff step instead of just the first one. The fix (adding randomized jitter) is a small code change that's easy to skip precisely because backoff-without-jitter looks like it's already working.

Principal Engineer Lens

"What happens when a thousand clients all fail at the same instant and all retry" is a question that separates reviewed-under-load designs from ones that only look correct on a single-client trace — the retry logic that works fine in a unit test or a manual curl is exactly the code path that determines whether a real incident self-heals in seconds or compounds into an hour-long outage. This transfers cleanly to trading systems and payment platforms, where a downstream exchange or processor outage triggering synchronized retries from every client can itself become the systemic event regulators ask about post-incident — the retry policy is part of the resilience design, not an implementation detail beneath it.

Sources:

Hermes Wiki