retrystorm · Learn · Interview practice · Glossary · Components

Home › Learn › Timeouts and Retries

Chapter 6 · Foundations · 25 min

Timeouts and Retries

When to give up, when to try again, retrying writes safely, circuit breakers, and how retries turn a rush into an outage.

Every weekday at 8:00 Inkwell sends a writing prompt, and a few seconds later a wave of users opens the app at once. For those few seconds the servers turn requests away, and the error graph has a spike at 8:00 every single morning.

Every request can fail or take too long. What the caller does next (wait, give up or try again) can turn a short rush into a small blip or into an outage that outlasts the rush itself.

Nobody waits forever

A timeout is how long a caller waits for an answer before giving up. Without one, a caller stuck on a slow server waits indefinitely, holding a connection and a user's patience the whole time. With one, the caller gets a clear failure it can act on: show an error, use a fallback, or try again.

The app stops waiting. The server doesn't know, and keeps working.

There's a catch that matters later in this chapter: giving up doesn't cancel anything. The request is still sitting in a server's queue, and the server will still spend its time on it, even though nobody is waiting for the answer any more.

See it happen: the app gives up after 150 ms, and the server needs 600 ms in all. Tell the server when the app gives up, then try delaying that message.

Retries hide brief failures

Many failures are brief. During a short rush, a server with a full queue turns requests away; a moment later it has room again. A retry sends the failed request again, and if it arrives after the rush, it succeeds and the user never notices.

The timing is everything. A retry sent immediately arrives in the same rush that rejected the first try, and gets rejected the same way. A retry sent after the rush ends, a few seconds later here, gets through.

Backoff and jitter

Exponential backoff waits longer before each new try: for example 1 s, then 2 s, then 4 s, up to a limit. A server that is struggling gets more and more room to recover.

Jitter makes each wait a random time up to that limit, so a thousand callers that failed at the same moment don't all come back at the same moment too.

TryBackoffWith jitter, waits
1stnoneright away
2nd1 sanywhere from 0 to 1 s
3rd2 sanywhere from 0 to 2 s
4th4 sanywhere from 0 to 4 s

Always cap the number of tries. Every retry is extra work for a server that is already short of capacity; a caller that retries forever never lets it recover.

Retrying writes safely

Retrying a read is harmless. Retrying a write may not be: if the first try timed out after the server saved it, the retry saves it again. Two posts, two orders, two payments.

A request is idempotent when sending it twice leaves the same result as sending it once ("set the title to X" is; "charge $5" isn't). Make non-idempotent writes safe to retry with an idempotency key: a unique id the client sends with the request, so the server can return the first answer to a repeat instead of doing the work again. The Idempotency chapter covers how it works.

RequestSafe to retry as is?
Open a noteyes
Set a note's title to "Draft 2"yes
Add a like, place an order, charge a cardonly with an idempotency key, or made idempotent another way

Retry storms

Now put the pieces together badly. Servers let requests wait in long queues; callers give up after 300 ms and retry. During a rush, requests wait longer than 300 ms, so callers give up and send them again. The servers now hold the original request (which nobody wants any more) and the retry. The queue grows, more requests time out, more retries arrive.

That loop is a retry storm. It can keep a system down long after the rush that started it has ended.

Circuit breakers

Backoff spaces out one caller's retries. A circuit breaker stops a caller from calling a service that is clearly failing at all. It counts recent failures and moves between three states:

StateWhat the caller doesMoves on when
Closedcalls as normalfailures pass a threshold: opens
Openfails at once, or uses a fallback, without callinga cool-down passes: half-open
Half-openlets a few trial calls throughthey succeed: closed; they fail: open again

An open breaker gives the struggling service room to recover and gives users a fast error or a fallback instead of a slow timeout. The cost is that some requests fail that might have worked; the threshold and cool-down decide how many. The chapter on circuit breakers builds one and tunes it.

Timeouts that fit the system

A timeout should be longer than a normal answer takes, including the waiting a busy moment adds, and short enough that users aren't left staring at a spinner. Too short, and callers abandon work the servers are about to finish.

The other half of the fix is on the servers: keep queues short, so an overloaded server turns requests away in a millisecond instead of letting them wait past every caller's timeout. A fast "no" is something a caller can retry later; a slow one is just wasted work.

In the wild

The ideas in this chapter, and where you'll meet them in real systems and interviews:

IdeaWhere you'll meet it
Backoff with jitterthe default retry policy in the AWS SDKs
Timeouts passed from call to callgRPC deadlines
Idempotency keysthe Idempotency-Key header in Stripe's API
Circuit breakersresilience4j, Envoy's outlier detection, Netflix Hystrix before it

Your practice

Every weekday at 8:00 Inkwell sends a writing prompt, and a few seconds later a wave of users opens the app at once. The app gives up after 300 ms and retries right away, and the morning rush ends with an error for one request in eight. You can't add servers. Change the app's retry policy so it rides out the rush.

Key takeaways

  • A timeout stops the caller waiting, but the server keeps working on the request.
  • A retry helps only if it lands after the brief failure has passed.
  • Back off longer each time, add jitter, and cap the number of tries.
  • Long queues plus short timeouts and retries feed a retry storm.
  • Set timeouts above a busy answer's time, and keep queues short so overload says no fast.

Read first

  • Scaling Out

Practise it

  • Ride Out the Rush: A daily rush overloads the servers for a few seconds. Make the app retry so the rush passes without errors or long waits.

Terms

  • Circuit breaker: Stops calling a failing service for a while, failing fast or using a fallback instead.
  • Exponential backoff: Waiting longer before each new retry: 1 s, 2 s, 4 s and so on.
  • Idempotency key: A unique id a client sends with a write, so the server can spot and skip a repeat.
  • Idempotent: A request that leaves the same result whether it runs once or twice.
  • Jitter: A random part of each retry's wait, so callers that failed together don't come back together.
  • Retry: Sending a failed request again, ideally after a backoff.
  • Retry storm: Retries of timed-out requests piling onto an overloaded server, keeping it down after the rush ends.
  • Timeout: How long a caller waits for an answer before giving up.
About · Contact · Privacy · Terms of use