HT
How Things Work
System Online

Designing for Failure

How resilient systems contain failure instead of cascading it — timeouts, backoff+jitter retries, circuit breakers, bulkheads, redundancy/failover, and graceful degradation.

How It Works

In a large system, components fail constantly — so resilience isn't about preventing failure, it's about containing it. The toolkit is layered: bound every call with a timeout, retry transient errors with backoff and jitter, trip a circuit breaker when a dependency is sick, isolate resources with bulkheads, run redundant instances with automatic failover, and always have a graceful fallback. Together they turn outages into degradations.

1
Assume Everything Fails

At scale, failure is constant — disks die, networks partition, dependencies slow down, instances crash. Designing for failure means building so that individual failures are contained and expected, not catastrophic. The goal is graceful degradation, not perfect uptime.

2
Timeouts & Retries with Backoff

Never wait forever: every remote call needs a timeout so a slow dependency can't exhaust your threads. Retry transient errors — but with exponential backoff and jitter, or synchronized retries become a self-inflicted DDoS (a retry storm) on an already-struggling service.

3
Circuit Breakers Stop Cascades

When a dependency keeps failing, a circuit breaker 'trips' (OPEN) and fast-fails subsequent calls with a fallback — sparing the sick service and freeing your resources. After a cooldown it goes HALF-OPEN to probe recovery, then CLOSES when healthy. This is what stops one failure from cascading across services.

4
Redundancy, Failover & Bulkheads

Eliminate single points of failure with redundancy across instances and availability zones, automatic failover to standby replicas, and bulkheads (isolated resource pools) so one overloaded component can't sink the rest. Combine with graceful degradation: serve a cached or simplified response rather than an error.

Key Concepts

🔌Circuit Breaker

Trips OPEN after repeated failures to fast-fail and spare a struggling dependency, then probes (HALF-OPEN) to recover.

Exponential Backoff + Jitter

Retrying with increasing, randomized delays so clients don't retry in lockstep and create a retry storm.

🪂Graceful Degradation

Serving a reduced experience (cached/default data) when a dependency is down, instead of failing the whole request.

🚢Bulkhead

Isolating resources (thread/connection pools) per dependency so one overloaded call path can't starve the others.

🔁Failover & Redundancy

Running standby replicas across zones and switching to them automatically when the primary fails — no single point of failure.

♻️Idempotency

Making operations safe to retry by ensuring repeated execution has the same effect as one — essential when retrying.

Timeout + retry + breaker + fallback
tsx
1// Resilient call: timeout → retry (with backoff+jitter) → circuit breaker → fallback
2async function getRecommendations(userId) {
3 return breaker.run(
4 async () => {
5 // 1. Bound the wait — never hang forever on a slow dependency
6 return await withTimeout(300, () =>
7 // 2. Retry transient errors with EXPONENTIAL BACKOFF + JITTER
8 retry({ attempts: 3, backoff: "exponential", jitter: true }, () =>
9 recoService.fetch(userId)
10 )
11 );
12 },
13 // 3. FALLBACK when the breaker is open or all retries fail —
14 // degrade gracefully instead of erroring the whole page
15 () => getPopularItems() // generic, cached, always-available
16 );
17}
18// breaker: trips after N failures, fast-fails while open, probes to recover
💡
Why This Matters

Reliability is the feature users notice when it's missing. Cascading failures and retry storms cause the biggest outages, and the patterns that prevent them — circuit breakers, backoff, bulkheads, graceful degradation — are core to SRE practice and a staple of senior system-design interviews. Designing for failure is what separates a demo from production.

Common Pitfalls

No timeouts — a single slow dependency exhausts your threads and takes everything down.
Retries without backoff/jitter — synchronized retries become a self-inflicted DDoS (retry storm).
Retrying non-idempotent operations — duplicates real side effects (double charges, double emails).
No fallback — the breaker opens but there's nothing to serve, so users still get errors.
Hidden single points of failure (one DB primary, one AZ, one cache) with no redundancy or failover.
Never testing failure — resilience that isn't exercised (chaos/game days) silently rots.
Real-World Use Cases

1The Cascading Failure

Scenario

A recommendations service slows to a crawl. The product page calls it synchronously, so page threads block waiting, thread pools fill, and soon the entire site is down — not just recommendations.

Problem

One slow, non-critical dependency consumed all the request-handling threads of a critical service. With no timeout, no breaker, and no fallback, a localized slowdown cascaded into a full outage.

Solution

Wrap the call in a tight timeout, a circuit breaker, and a bulkhead (separate thread pool). When recommendations degrade, the breaker opens and the page instantly falls back to generic 'popular items' from cache. The product page stays fast and fully functional.

💡

Takeaway: Isolate and time-bound every dependency, especially non-critical ones. A circuit breaker plus a fallback converts 'the whole site is down' into 'one widget shows generic data'.

2The Retry Storm

Scenario

A database has a brief hiccup. Thousands of clients all retry immediately and simultaneously, hammering the database the instant it tries to recover and knocking it back down — repeatedly.

Problem

Naive fixed-interval retries meant every client retried in perfect lockstep. The synchronized surge turned a 2-second blip into a prolonged outage — clients DDoS'd their own backend.

Solution

Switch to exponential backoff with jitter so retries spread out over time, cap the number of attempts, and add a circuit breaker so clients stop retrying entirely once failures pile up. The database gets breathing room to recover.

💡

Takeaway: Retries without backoff and jitter make outages worse. Randomized, capped, backoff-based retries — plus a breaker — let a struggling system actually recover instead of being re-overwhelmed.

Join the discussion

Comments

?

Loading comments...

Cookie preferences

We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.