Operations Reviewed

Timeouts, retries and backoff

Every remote call needs a timeout, a deadline it inherits from its caller, and a retry policy that cannot multiply load when a dependency is struggling. Retry at one layer, a few times, with jittered exponential backoff and a budget that stops retries when they stop helping.

On this page
  1. Timeouts
  2. Deadlines and cancellation
  3. When to retry
  4. Retry budgets
  5. Backoff and jitter
  6. Where sources differ
  7. A retry policy to start from
  8. Worked example
  9. Hedged requests
  10. Retry storms and cascading failure

Timeouts

Set a timeout on every call to another process, covering both connecting and the request itself. A call without one holds a thread, a connection and memory for as long as the dependency stays silent. The timeout value also caps how much work piles up: requests in flight = arrival rate × time each is held. A dependency that hangs turns a long timeout into thousands of stuck requests.

AWS’s approach is to choose an acceptable rate of false timeouts, the share of calls that would have succeeded but are cut off, and set the timeout at the matching percentile of the dependency’s latency: 0.1% false timeouts means the p99.9. Adjust it in three cases:

The SRE book warns against timeouts several orders of magnitude above the normal latency: if a small share of requests stop completing, they hold threads for the whole timeout and starve the requests that would have succeeded. Failing fast, with an immediate error when a backend is known to be down, avoids the wait altogether.

Deadlines and cancellation

A per-hop timeout chosen in isolation can outlive the request it serves. With deadline propagation, the frontend sets one absolute deadline and every downstream call gets whatever time is left, less a little for the network and for processing the reply. A server that receives a request whose deadline has passed should drop it: the caller has given up, so the work earns nothing. For multi-stage requests, check the remaining time before each stage.

Cancellation propagation does the same for requests that can no longer succeed: when a call fails permanently or a hedged copy wins, tell the other branches of the call tree to stop. Some gRPC implementations propagate deadlines automatically (Java and Go by default, C++ when enabled), sending each hop the time remaining rather than a clock time. Even then, the server code must stop the work it started when a call is cancelled; check what your own framework does.

When to retry

Most failures in distributed systems are partial or brief, so a second attempt often succeeds. AWS calls retries “selfish”: each one spends more of the server’s capacity to raise one client’s chance of success. That is a good trade when failures are rare and a bad one when the server is failing because it is overloaded.

Retry budgets

A per-request limit is not enough. If a whole cluster rejects most requests, three attempts each still nearly triples the load on it. A retry budget limits retries across all requests from a client or process:

All three let retries cover scattered, transient errors and switch them off when errors are widespread, which is exactly when retries make things worse.

Backoff and jitter

Retrying immediately and repeatedly hits a struggling server at the worst moment. Capped exponential backoff doubles the wait before each retry up to a maximum. On its own it still synchronises clients: everything that failed together retries together. Jitter spreads them out. In AWS’s simulation, “full jitter”, a uniformly random wait between zero and the capped backoff, did the least work of the methods tested; “decorrelated jitter” finished slightly sooner for slightly more calls, and un-jittered backoff was the worst on both.

wait before retry n = random(0, min(cap, base × 2^(n − 1)))

RetryLongest waitMean wait
125 ms12.5 ms
250 ms25 ms
3100 ms50 ms
4200 ms100 ms
5400 ms200 ms
6800 ms400 ms
71,000 ms500 ms

Base 25 ms and cap 1 s, as in the policy below; with full jitter the mean wait is half the longest. Pick the cap so the attempts you allow still fit inside the caller’s deadline.

Add jitter to periodic work too: cron jobs, cache refreshes and clients that poll once a minute line up on the first seconds of the minute or the hour. For scheduled work AWS picks jitter that stays the same on each host rather than drawing it at random, so any overload it causes repeats in a pattern that people can spot.

A retry policy to start from

These values are starting points from the sources above, not standards. Record what you choose next to the client code.

# Starting point for one client calling one dependency. Tune it from your own data.
timeout:   the dependency's p99.9 latency, for about 0.1% false timeouts;
           add padding if p99.9 is close to p50; cover connect, TLS and DNS
deadline:  pass the caller's remaining deadline down; never wait longer than it
attempts:  3 per request (1 + 2 retries), at one layer only: the one
           directly above the dependency that failed
retry:     connection failures, timeouts and transient server errors, and only
           for idempotent requests or ones that carry an idempotency key
no retry:  malformed or unauthorised requests, permanent errors, and any
           "overloaded, don't retry" signal; honour Retry-After when sent
backoff:   full jitter: wait random(0, min(cap, base × 2^(n − 1))) before retry n,
           base 25 ms, cap 1 s
budget:    retries at most 10% of requests per client (or a token bucket);
           when it is spent, fail fast instead of retrying

Worked example

Fit a timeout and retries inside a 1-second deadline

Checkout calls a fraud-scoring service at 2,000 requests per second and must answer within 1 s. Yesterday the scoring service handled 1,000,000 calls: 950,000 at 30 ms, 45,000 at 60 ms, 4,000 at 120 ms, 900 at 180 ms, and 100 at 1,500 ms.

  1. Accept 0.1% false timeouts, so take the p99.9: the 999,000th fastest call.p99.9 = 120 ms, p50 = 30 ms; calls slower than 120 ms = 0.1%
  2. Allow 3 attempts with full-jitter backoff, base 25 ms. The longest the call can take:3 × 120 ms + 25 ms + 50 ms = 435 ms
  3. If the scoring service hangs, the timeout caps what piles up in checkout:2,000/s × 120 ms = 240 requests waiting at once, against 60,000 with a 30 s default
  4. If it fails outright and 2 layers above checkout also make 3 attempts each:33 = 27 calls to the scoring service per user action Retrying at checkout alone, behind a 10% retry budget, sends about 1.1× the first attempts.

A 120 ms timeout with 3 attempts fits the deadline with 565 ms to spare, and holds 240 requests rather than 60,000 when the dependency hangs. Retry at checkout only, behind a budget, and have the layers above pass the deadline down instead of retrying.

Hedged requests

A hedged request is a retry sent before the first attempt fails: after a short delay, send a second copy to another replica and use whichever answers first. Dean and Barroso suggest waiting until the first request has been outstanding longer than the 95th percentile for its kind, which limits the extra load to about 5%. The same rules apply as for retries: idempotent operations only, cancel the loser, and cap the extra load. See latency percentiles for the measured effect on the tail.

Retry storms and cascading failure

A cascading failure grows through positive feedback: an overloaded server fails, its load moves to the others, and they fail in turn. Retries feed it. Requests that time out are sent again, the server spends its capacity on work whose callers have already given up, and load stays high after the original trigger has gone. Signs during an incident:

The SRE book notes that retry graphs can be mistaken for a symptom rather than a cause. Fixing retry behaviour usually needs a code push, so the immediate options are to cut load sharply, or stop traffic and ramp it back gradually; see overload and load shedding. Graph retries separately from first attempts so the gap is visible.

Sources: Google, Site Reliability Engineering, 1st ed., ch. 22 “Addressing Cascading Failures” and ch. 21 “Handling Overload”; Marc Brooker, Timeouts, retries, and backoff with jitter (Amazon Builders’ Library) and Exponential Backoff And Jitter (AWS Architecture Blog, 2015); Jeffrey Dean and Luiz André Barroso, The Tail at Scale (Communications of the ACM, 2013); gRPC, Deadlines.