Resilience and Reliability Patterns
Assume everything fails. Combine timeouts, retries, breakers, bulkheads, load shedding and graceful degradation into a coherent toolbox, step down a degradation ladder during an incident, and verify it all with chaos engineering.
🎯 What you'll learn
- Design assumptions for distributed failure
- The pattern toolbox and how patterns interact
- Bulkheads, load shedding and prioritization
- Graceful degradation ladders and chaos engineering
Reliability is the probability a system does what users need, when they need it. Resilience is how it behaves when parts fail — and in distributed systems parts always fail. This topic collects the patterns into one toolbox and shows how they combine.
1Design Assumptions
- Every network call can fail, be slow, or succeed without you knowing (
06-failure_handling_and_retry_strategies/). - Dependencies will be down at some point; your service's availability is bounded by synchronous dependencies (availability of a chain ≈ product of availabilities).
- Load will exceed capacity at some point (spikes, retries, bugs).
- Humans will deploy bad changes.
The goal: contain failures (blast radius), degrade gracefully, and recover automatically.
2The Pattern Toolbox
| Pattern | Problem it addresses | Mechanism |
|---|---|---|
| timeouts | slow dependencies hold resources forever | bound every wait |
| retries with backoff + jitter | transient failures | bounded, idempotent, one layer |
| circuit breaker | repeatedly calling a dead dependency | fail fast while open; probe to recover (distributed-systems/fundamentals/07-circuit_breaker) |
| bulkhead | one dependency/tenant exhausting shared resources | separate pools/queues/concurrency limits per dependency or tenant |
| rate limiting | clients overloading you | token buckets per client (backend/api-design/07-rate_limiting) |
| load shedding | overload of the whole service | reject early (429/503) when queues/latency exceed thresholds; prioritize critical traffic |
| backpressure | fast producers, slow consumers | bounded queues, pull-based consumption (distributed-systems/fundamentals/05-backpressure) |
| fallback / graceful degradation | dependency down | cached/stale data, defaults, feature turned off |
| redundancy & failover | instance/AZ/region failure | N+1 instances, multi-AZ, active-active/passive (distributed-systems/fundamentals/10-…) |
| idempotency | retries and duplicates | idempotency keys, dedup, upserts |
| queue-based load leveling | spiky load | buffer work in a durable queue |
| health checks & self-healing | stuck instances | liveness/readiness, auto-replacement (devops-cloud/observability/04-health_checks) |
| static stability | control plane down | data plane keeps working on last-known config |
| safe deployments | bad changes | canaries, feature flags, automatic rollback (devops-cloud/cicd/02-deployment_strategies) |
3Bulkheads
Named after ship compartments: a breach floods one compartment, not the ship.
const pools = {
payments: new Semaphore(20), // at most 20 concurrent calls to payments
recommendations: new Semaphore(5), // a slow recommender can tie up at most 5 slots
};
async function callRecommendations(userId: string) {
if (!pools.recommendations.tryAcquire()) return FALLBACK_RECOMMENDATIONS; // full → degrade, don't queue
try { return await withTimeout(recoClient.get(userId), 200); }
catch { return FALLBACK_RECOMMENDATIONS; }
finally { pools.recommendations.release(); }
}Without the bulkhead, a slow recommendation service consumes every worker/connection, and checkout (which needs payments) fails too.
4Load Shedding and Prioritization
When demand exceeds capacity, somebody won't be served. Decide who, deliberately:
- reject early and cheaply (before parsing bodies or touching the DB),
- use queue time or in-flight count as the signal (concurrency limits like Netflix's adaptive limiters),
- prioritize: checkout > browsing > analytics beacons; authenticated > anonymous; interactive > batch,
- return
503/429withRetry-Afterso well-behaved clients back off.
Serving 80% of requests well beats serving 100% of requests so slowly that they all time out (goodput vs throughput).
5Graceful Degradation Ladder
Plan what the product does as dependencies fail:
all healthy → full experience
recommendations down → show bestsellers (static/cached)
search down → category browsing only
payments slow → accept order, "payment processing" state, confirm asynchronously
inventory down → allow orders for items with stale stock > threshold, reconcile later
database read replica down → serve reads from primary with stricter rate limitsEach step needs a feature flag / kill switch and is exercised in tests and game days.
6Verifying Resilience: Chaos Engineering
Untested resilience mechanisms usually don't work. Chaos engineering runs controlled experiments: hypothesize steady state ("checkout success ≥ 99.5%"), inject a failure (kill pods, add 500 ms latency to payments, black-hole the cache, fail an AZ), observe, and fix what breaks. Start in staging, with small blast radius and an abort switch; graduate to production game days. Tools: Chaos Mesh, Litmus, AWS FIS, Gremlin, Toxiproxy (for local tests).
Under the HoodWhy Resilience Mechanisms Interact▾
Patterns compose — and can fight. A retry policy inside a circuit breaker: should failed retries count as one failure or several? (Usually the breaker wraps the whole retried call, so persistent failure opens it quickly.) A timeout shorter than the dependency's p99 turns slow-but-successful calls into failures that trigger retries, increasing load on the slow dependency (metastable failure: the system stays degraded even after the original trigger disappears, because retries and timeouts sustain the overload). Load shedding and autoscaling interact: shedding hides demand from CPU-based scaling signals. The safe composition order from the caller's view is usually: bulkhead (concurrency limit) → circuit breaker → timeout → retry (budgeted) → fallback, with metrics on each stage so you can see which one acted.
⚠️ Common Pitfalls
- Resilience features that are never tested — fallbacks that throw, breakers that never open.
- Unbounded queues "for resilience" — latency explodes, then memory.
- Shared pools for critical and non-critical dependencies.
- Timeouts longer than the caller's timeout.
- Retries + short timeouts on an overloaded dependency — metastable failure.
- Fallbacks that hit the same failing dependency (e.g. "fallback" to the same DB).
📋 Quick Reference
contain: bulkheads · rate limits · load shedding · backpressure
detect & stop: timeouts · circuit breakers · health checks
recover: retries (budgeted, idempotent) · failover · self-healing · safe deploys
degrade: fallbacks · degradation ladder · kill switches
verify: chaos experiments · game days
order: bulkhead → breaker → timeout → retry → fallback🧠 Key takeaways
- Assume partial failure: every dependency can be slow, wrong or down.
- Toolbox: timeouts, retries, circuit breakers, bulkheads, load shedding, caching, fallbacks, which interact (tune them together).
- Shed load by priority (checkout before recommendations) before the system collapses.
- Pre-build a degradation ladder and verify everything with chaos experiments and game days.
🎤 Interview Q&A
Q1Name the key resilience patterns.core▾
Timeouts, retries with backoff and jitter, circuit breakers, bulkheads, rate limiting and load shedding, fallbacks/graceful degradation, idempotency, health checks with automatic failover, and queue-based load leveling.
- Which is the most important? Timeouts: without them every other pattern fails.
Q2What is load shedding?core▾
Deliberately rejecting some requests when overloaded (fast 503/429) to keep serving the rest within latency targets, instead of slowing down for everyone. Prioritize critical traffic (checkout over recommendations).
- How do you decide what to shed? Request priority classes and concurrency or latency thresholds.
Q3What is graceful degradation? Give examples.core▾
Continuing to provide core functionality when parts fail: showing cached prices with a notice, hiding recommendations if that service is down, queuing orders for later processing, or switching to read-only mode during a database failover.
- How do you implement it? Feature flags, fallbacks behind circuit breakers, and clear UI states.
Q4What is chaos engineering?senior▾
Running controlled experiments that inject failures (killing instances, adding latency, dropping network) to verify the system behaves as expected and to find weaknesses before real incidents. Start small, in staging, with a hypothesis and a way to abort.
- Famous example? Netflix's Chaos Monkey.
Q5How do you measure reliability?core▾
With SLIs and SLOs (availability, latency, correctness), error budgets, MTTD/MTTR, incident frequency and severity. Reliability work is prioritized when the error budget is burning.
- What's MTTR? Mean time to restore service after a failure.
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.
✅ Check yourself
Answer the questions — feedback is instant.