All chapters in this track
- 01Chapter 1: Why Reliability and Scale Matter
- 02Chapter 2: Reliability Vocabulary: SLAs, SLOs, SLIs, and Error Budgets
- 03Chapter 3: Redundancy and Fault Tolerance: Eliminating Single Points of Failure
- 04Chapter 4: Horizontal vs. Vertical Scaling, and Load Balancing/Auto-Scaling
- 05Chapter 5: Caching for Scale: Cache Stampede and Invalidation at Scale
- 06Chapter 6: Database Scaling: Replication, Partitioning, Read Replicas, Connection Pooling
- 07Chapter 7: Resilience Patterns: Timeouts, Retries, Circuit Breakers, Bulkheads, Load Shedding
- 08Chapter 8: Observability: Metrics, Logging, Distributed Tracing, and Actionable Alerts
- 09Chapter 9: Incident Response: Severity, Incident Command, Communication, Blameless Postmortems
- 10Chapter 10: Chaos Engineering: Proactively Testing for Failure
- 11Chapter 11: Capacity Planning and Load Testing
- 12Chapter 12: Core Trade-offs: Consistency vs. Availability, Cost vs. Redundancy, Simplicity vs. Resilience
- 13Chapter 13: Incident Case Study: The Cascading Failure
- 14Chapter 14: Incident Case Study: The Silent Data Corruption
- 15Chapter 15: Common Interview Questions About Reliability and Scaling
Chapter 13 of 15
Chapter 13: Incident Case Study: The Cascading Failure
Why this matters
This case study ties together Chapters 3, 6, 7, 8, and 9 into one realistic incident narrative — showing exactly how a missing resilience pattern (Chapter 7) turns a small, contained failure into a full, cascading outage, and exactly what a genuinely systemic (not individual-blaming) postmortem changes afterward.
The scenario
An e-commerce platform's checkout flow depends on three internal services: OrderService, InventoryService, and RecommendationService (which suggests related items during checkout — a non-critical enhancement, not required for checkout to succeed). All three share a single, common connection pool to the primary database (Chapter 6).
What went wrong
14:02 — RecommendationService's underlying machine-learning model begins returning malformed data due to an unrelated, upstream data pipeline bug (not itself part of this postmortem's focus). Every call to RecommendationService now takes 8-10 seconds to time out, instead of its normal ~50ms response time.
14:04 — OrderService calls RecommendationService synchronously during checkout (a design decision, at the time, that seemed reasonable — "recommendations should appear alongside the order confirmation"). Every checkout request is now waiting 8-10 seconds on a slow, failing dependency that isn't even required for the checkout to succeed.
14:06 — Because OrderService's calls to RecommendationService are consuming threads from the shared connection/thread pool (no bulkhead isolation, Chapter 7) that InventoryService calls also depend on, the pool becomes exhausted. InventoryService calls — which ARE required for checkout — start failing too, not because InventoryService itself is unhealthy, but because there are no available connections/threads left in the shared pool to make the call at all.
14:08 — Checkout is now failing for effectively all users, not just experiencing degraded recommendations. The metrics dashboard (Chapter 8) shows checkout error rate spiking from near-zero to over 90%.
14:10 — On-call engineer is paged (SEV1, per Chapter 9's severity criteria — complete outage of a revenue-critical function). An incident commander is assigned separately from the debugging engineer (Chapter 9's role separation).
14:15 — Distributed tracing (Chapter 8) reveals that failing checkout requests are spending nearly all their time waiting on RecommendationService calls specifically, not on InventoryService or the database directly — this narrows the investigation immediately.
14:18 — The team disables the recommendation-fetching call entirely via a feature flag (companion System Design book, Chapter 9) — checkout immediately recovers, since it no longer waits on the failing dependency at all.
14:20 — Incident declared resolved. Total impact: 18 minutes of near-complete checkout failure.
The postmortem: going past the proximate cause
Proximate cause (where a weak postmortem would stop): "RecommendationService had a bug causing it to respond slowly."
Continuing to ask why (per Chapter 9's systemic-factor discipline):
- Why did a slow, non-critical dependency cause checkout to fail entirely? →
OrderServicecalled it synchronously, on the critical path, even though recommendations aren't required for checkout to succeed. - Why did
RecommendationService's slowness affectInventoryService, an unrelated, healthy dependency? → They shared a single connection/thread pool with no bulkhead isolation between them. - Why wasn't this caught before it happened in production? → No chaos engineering experiment (Chapter 10) had ever tested "what happens if
RecommendationServicebecomes slow" — this specific failure mode had never been verified, only assumed to be safely non-critical.
What the postmortem changed
## Action items from this postmortem
1. Move the RecommendationService call to be ASYNCHRONOUS and
off the critical checkout path entirely -- checkout should never
wait on a non-critical enhancement (companion System Design book,
Chapter 8's synchronous-vs-asynchronous discipline).
2. Implement bulkhead isolation (Chapter 7) -- separate connection/
thread pools for calls to RecommendationService versus
InventoryService, so ONE dependency's failure can never again
exhaust resources needed by an unrelated, healthy dependency.
3. Add a chaos engineering experiment (Chapter 10) that specifically
simulates RecommendationService becoming slow/unresponsive, run
regularly, to verify this class of failure stays contained going
forward -- not just fixed once, but continuously VERIFIED.
4. Add a circuit breaker (Chapter 7) around the RecommendationService
call specifically, as defense in depth alongside making it
asynchronous -- even an asynchronous call shouldn't be allowed to
retry indefinitely against a known-failing dependency.
Why this postmortem is a model of the systemic-factor discipline
[SENIOR INSIGHT] Notice that the postmortem's action items address three independent layers of the failure, not just "fix the one bug that started it": making the call asynchronous (removing the dependency from the critical path entirely), adding bulkhead isolation (preventing resource-sharing cascades even if a future dependency has a similar issue), and adding chaos testing (proactively verifying this class of failure stays contained, rather than trusting the fix to remain correct forever without ongoing verification). A weaker postmortem would have stopped at "we fixed the RecommendationService bug" — which addresses only this one specific incident's trigger, leaving the underlying structural vulnerabilities (synchronous coupling to a non-critical dependency, shared resource pools with no isolation) fully intact and ready to cause the next, different cascading failure via some other future trigger.
Common mistakes
- Stopping the postmortem at "fix the specific bug that triggered this incident", leaving the structural vulnerabilities (synchronous coupling, no bulkhead isolation) that turned a small trigger into a full cascading failure completely unaddressed.
- Not recognizing the shared connection pool as the actual mechanism of cascade, focusing entirely on why
RecommendationServicewas slow rather than why its slowness was able to affect unrelated, healthy services. - Treating this incident as unique/unlikely to recur, rather than recognizing that the exact same structural vulnerability (synchronous coupling to a non-critical dependency, no bulkhead isolation) could be triggered by a completely different future failure in a completely different dependency.
- Not adding proactive, ongoing verification (chaos engineering) after the fix, leaving the fix's continued correctness as an unverified assumption rather than an ongoing, tested guarantee.
Chapter summary / cheat sheet
- A non-critical dependency's slowness (
RecommendationService) cascaded into a complete outage of a critical function (checkout) because of two structural vulnerabilities: synchronous coupling to a non-critical call, and a shared resource pool with no bulkhead isolation. - Distributed tracing (Chapter 8) was what let the team quickly localize the problem to the specific dependency, rather than searching blindly.
- A genuinely systemic postmortem addresses multiple independent layers of the failure (make the call async, add bulkheads, add circuit breakers, add ongoing chaos verification) — not just the one bug that happened to trigger this specific incident.
- This case study demonstrates nearly every technique from Chapters 3-10 working together in one realistic, coherent narrative — resilience patterns, observability, incident response process, and postmortem discipline.