Architecture patterns
Retry policy and backoff with jitter
Idempotency makes retries safe, and a retry policy makes them useful. Learn which errors to retry, how to space attempts with exponential backoff and full jitter, and when to stop.
A retry is only safe when the operation is idempotent. A retry policy decides which errors are worth retrying, how long to wait between attempts, and when to give up. Without one, retries make bad outages worse.
Concept
Exponential backoff waits longer after each failed attempt, such as 100 ms, 200 ms, 400 ms. Jitter randomizes each wait so clients don't all retry at the same instant.
Which errors to retry
| Response | Retry? | Why |
|---|---|---|
| Timeout or connection reset | Yes, if idempotent | Unknown outcome, often transient |
| 429 Too Many Requests | Yes, after Retry-After | The server told you when |
| 502, 503, 504 | Yes | Gateway or overload, usually transient |
| 500 | Carefully | May be a bug that fails every time |
| 409 Conflict, in progress | Yes, with backoff | The first attempt is still running |
| 400, 401, 403, 404, 422 | No | The request itself is wrong |
Why jitter matters
Retrying immediately is the worst option. If a database blips for two seconds and 10,000 clients retry at once, the recovering database gets a synchronized wave the moment it returns, and falls over again.
Exponential backoff spreads attempts out. Jitter spreads the clients out too. Marc Brooker's analysis on the AWS Architecture Blog compared jitter strategies by simulation. It found that "full jitter", a random wait between zero and the exponential bound, finished the work with far fewer total calls than backoff without jitter.
base = 100 ms, cap = 10 s, full jitter: sleep = random(0, min(cap, base × 2^attempt))
attempt 0: bound 100 ms → sleep somewhere in 0 to 100 ms
attempt 1: bound 200 ms → 0 to 200 ms
attempt 2: bound 400 ms → 0 to 400 ms
attempt 3: bound 800 ms → 0 to 800 ms
attempt 4: bound 1.6 s → 0 to 1.6 s
attempt 7: bound 12.8 s → capped at 10 s, so 0 to 10 sThe sketch below applies full jitter and respects the caller's deadline.
func withRetry(ctx context.Context, attempts int, fn func(context.Context) error) error {
const base, maxDelay = 100 * time.Millisecond, 10 * time.Second
var err error
for attempt := 0; attempt < attempts; attempt++ {
if err = fn(ctx); err == nil || !isRetryable(err) {
return err
}
bound := min(maxDelay, base<<attempt)
sleep := time.Duration(rand.Int63n(int64(bound))) // full jitter
select {
case <-time.After(sleep):
case <-ctx.Done(): // don't retry past the caller's deadline
return ctx.Err()
}
}
return err
}The deadline check matters. Retrying after the original caller has given up is pure waste.
Common mistakes
- Retrying instantly. It synchronizes clients into waves that hit the recovering system together.
- Retrying errors that can't change. A 400 or 404 fails the same way every time.
- Retrying a non-idempotent call. Backoff doesn't make a second charge safe.
- Ignoring the deadline. A retry that finishes after the caller left is wasted work.
Check yourself
1. Why wait the full Retry-After on a 429 instead of retrying sooner?
The server has told you when it can accept the request. Retrying earlier only adds load it has already refused.
2. What does full jitter buy you that plain backoff doesn't? It spreads retries randomly across the window, so clients don't arrive at the server in synchronized waves.
Key takeaways
- Retry only errors that might change on the next attempt, and only idempotent operations.
- Exponential backoff with full jitter spreads retries out in time.
- Stop at the caller's deadline.
- Limit how much retrying the whole system does, which the next page covers.
Further learning
The AWS Architecture Blog post explains the jitter comparison and its simulation results.
Exponential backoff and jitter (AWS Architecture Blog)Update (May 2023): After 8 years, this solution continues to serve as a pillar for how Amazon builds remote client libraries for resilient systems. Most AWS SDKs now support exponential backoff and jitter as part of t…Amazon Web Services
Related
Idempotency keys
For operations that can't be made idempotent by design, a client-chosen key lets the server recognize a retry and replay the first answer. Learn the protocol and its failure cases.
Retry budgets and retry amplification
Retries at several layers multiply load exactly when a system is weakest. Learn the multiplication, and how to cap it with one retry layer and a retry budget.
Resilience patterns
The toolkit for keeping one failure from becoming an outage, from timeouts and retries to circuit breakers, bulkheads, load shedding and cells. Start here for the resilience map.