SevyDevy

Module 7 · Keeping It Running and Safe · Lesson 26 of 35

How to handle failures in distributed systems: timeouts, retries and breakers

14 minFree System design course · Beginner to intermediateLesson 7.1By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

  • How to handle failures in distributed systems: assume any call can fail or turn slow. Limit every wait, retry in one place, and pause calls to a sick part.
  • Think of a jammed toll plaza that clears when the traffic police stop entry for a while.
  • A slow part is often worse than a dead one, and 3 tries at each of 5 layers is 243 calls.
Soon

Video lesson · coming soon

How to handle failures: timeouts, retries and circuit breakers

Lala, your AI mentor, will walk you through this lesson in a short animated video. Until then, everything is here to read and try.

  1. 01 Slow, not down, and the site stopped
  2. 02 A jammed toll plaza
  3. 03 The overload loop with a valve
  4. 04 Watch the site fall, then hold
  5. 05 Sandeep’s four guards
  6. 06 Next: saying slow down to bots

At noon the national T20 final sale opens: 1 crore people try for 1.2 lakh seats. The payment gateway never goes down. It only gets slow. Yet SeatJaldi stops answering everything, even the seat map and login.

It stays that way for 22 minutes, and about ₹45 lakh of bookings are lost. Last week, to be safe, Arjun Nair set every layer of the booking path to “try up to 3 times”. A tap on Pay passes through five layers, from the phone app to the payment client that calls the payment gateway.

“Why did everything stop if only payments were slow?” Meera Kulkarni asks. “And what do I tell users?” That afternoon Sandeep Patil walks in. He is SeatJaldi’s new SRE, a site reliability engineer, who keeps the site running.

Sandeep reads Zoya Mirza’s dashboards for ten minutes. “The payment gateway did not fail,” he says. “It took its time, and we waited for it with everything we had. Twenty-two minutes of everything. Next time, call me. Preferably not at 3 am.”

Arjun stares at the whiteboard. SeatJaldi has four servers and a load balancer. How did one slow part take all of them down?

How to handle failures in distributed systems?

Assume every call can fail or turn slow. The one line to keep: retries and overload feed each other, so break the loop on purpose.

  • A part turns slow, so calls to it wait and pile up.
  • Callers try again, adding load, so it slows further.
  • A valve on the loop breaks it.

The overload loop with a valve

Slow replies make calls wait, workers pile up, and retries add load. A valve pauses calls and sends fans to a plan B.

Diagram connections:

  • Slow payment gateway to Workers pile up (Calls wait)
  • Workers pile up to Every layer retries (Fans tap again)
  • Every layer retries to Valve: pause calls (Retries arrive)
  • Valve: pause calls to Slow payment gateway (Few calls pass)
  • Valve: pause calls to Plan B for the fan (Shut: skip call)
  • Slow payment gateway: Replies in 10 seconds, not 0.2.
  • Workers pile up: Each waiting call keeps a worker busy.
  • Every layer retries: Each retrying layer multiplies the calls.
  • Valve: pause calls: After 5 failures it shuts off calls for 30 seconds.
  • Plan B for the fan: Holds the seat 10 minutes; the fan pays soon.
  • You tap Pay, the spinner turns, you tap again, then “something went wrong”. The call had no time limit, and each tap added one.
  • On a match night a food app freezes, and even the menu will not open. Every worker was waiting on one slow part.

Why is a slow service worse than a dead one?

A dead service usually fails at once, so callers move on. A slow one makes every caller wait, each holding a worker. A closed toll plaza sends cars elsewhere; a crawling one jams the roads behind it.

Little’s law: calls in flight (started, not yet finished) = calls arriving each second x seconds per call. Say each server on the booking path gets 50 payment calls a second and has 200 workers (assumptions).

  • Normal, 0.2 seconds: 50 x 0.2 = 10 calls in flight.
  • Slow, 10 seconds: 50 x 10 = 500 calls in flight.
  • 200 workers are all busy after 200 / 50 = 4 seconds, and the seat map and login need them too.

The first guard is a time limit, like the driver who gives up and takes the old road.

Try it yourselfWatch one slow payment gateway take down the siteStep through the national final sale and watch the loop form, then break.

Timeout vs retry: what does each one do?

A timeout decides when to stop waiting; a retry decides whether to try again. Every call needs the first, and only one layer the second. Step 3 showed why: when every layer retries, one tap becomes 243 calls.

Retry next to the failing call, as in step 6, and not the instant a try fails, like a driver honking.

Step 6 also waited a random time. If every caller waits exactly 200 milliseconds, they all return together, like drivers rejoining at once.

If the part stays sick, even careful retries keep it busy. Step 7’s valve, the traffic police, is a circuit breaker. Engineers say it “opens” when it stops calls, like a switch breaking a circuit.

Meanwhile the fan still needs an answer, like the plaza’s board: “Closed, pay later by app.” That was step 8.

How do engineers set timeouts, retries and circuit breakers?

Set each timeout a little above the slowest normal reply, retry next to the failing call, and open a circuit breaker after repeated failures.

  • Timeout: choose how many false timeouts you accept, say 1 in 1,000, and read that percentile, p99.9. If it is 1.5 seconds (assumed), 2 seconds fits.
  • Nested calls: send one “finish by” deadline with the call, so each layer knows the time left and stops in time.
  • Retries: 3 tries at 5 layers is 3^5 = 243 times the load. Add a retry budget, a cap on retries: 60 a minute per server, about 2 in 100 calls. In flight stays near step 5’s 100.
  • Circuit breaker: open after 5 failures in 10 seconds and wait 30 (assumed). Then let one test call through, like the police letting in a few cars. Alert when it opens, and test that state on purpose, since it rarely runs.
  • Cascading failures often start with a deploy or settings change hitting every server at once, so roll changes out a few servers at a time.
python
import random, time, requests

def charge(booking_id, amount, idem_key):
    for attempt in range(3):
        try:
            return requests.post(
                "https://pay.example/payments",
                json={"booking_id": booking_id, "amount": amount},
                headers={"Idempotency-Key": idem_key},
                timeout=2,
            )
        except (requests.Timeout, requests.ConnectionError):
            if attempt == 2:
                break
            time.sleep(random.uniform(0, 0.1 * pow(2, attempt)))
    raise RuntimeError("payment gateway not answering")

What just happened? Up to 3 tries, each with a 2-second timeout, and random waits of up to 0.1, then 0.2 seconds. A 503 reply (service unavailable) is returned, not retried, so check it. The app makes the key once, when the Pay screen opens, and reuses it on every tap and retry.

Here timeout=2 limits connecting and each gap while the reply arrives, not the whole call. Rarely used paths tend to break, so fake a payment gateway that always times out, and expect 3 calls, then the error.

Try it yourself

  1. Pick the numbers

    Your server gets 40 payment calls a second and has 150 workers. One layer makes 3 tries of up to 2 seconds, waiting up to 0.1, then 0.2 seconds.

  2. Work it out

    If every try times out, how long at most is a worker busy, and how many calls are in flight? Now all 5 layers get “retry 3 times”: a try plus 3 retries. How many reach the payment gateway?

  3. Check

    You are right with 6.3 seconds, 252 in flight (over 150 workers) and 4 x 4 x 4 x 4 x 4 = 1,024 calls.

At noon, with no timeout, each payment call held a worker for 10 seconds: 500 in flight for 200 workers. Four servers and a load balancer did not help; all waited on the same slow part.

Then 3 tries at 5 layers turned one tap into 243 calls, like honking at a jammed plaza. Sandeep’s rebuild breaks the loop on purpose: time limits, one retry layer, a circuit breaker and a fallback. “Next time the payment gateway takes its time,” he says, “we stop waiting with everything we have.”

Meera tells users: “Your seat is safe for 10 minutes. Payment will finish in a moment.” But with circuit breakers in place, bots still hit the booking API at 50x normal. How do we say “slow down” politely?

Check yourself

Check yourself

A payment gateway is slow, not down. There are no timeouts, and every layer tries 3 times. What happens to its load?

Pick an answer to see why it is right or wrong.

Check yourself

Where should the retry live in a five-layer booking path?

Pick an answer to see why it is right or wrong.

Remember these three

  1. Retries and overload feed each other, so break the loop on purpose.
  2. A slow part often hurts more than a dead one, so put a timeout on every call.
  3. Retry in one layer with exponential backoff and jitter; add a circuit breaker and a fallback.

Questions people ask

How to handle failures in distributed systems?
Assume every call can fail or turn slow. Put a timeout on each call and retry in one layer only, with exponential backoff and jitter. Add a circuit breaker and a fallback.
What is a retry storm?
A retry storm is many callers retrying at once and overloading a part that was already struggling. Five layers with 3 tries each turn one request into 243.
What is the difference between a timeout and a retry?
A timeout decides when to stop waiting; a retry decides whether to try again. Use a timeout on every call, and a retry in only one layer.