Vasu Patel logoVasu Patel
← Built to Last: Reliability & Scaling
All chapters in this track
  1. 01Chapter 1: Why Reliability and Scale Matter
  2. 02Chapter 2: Reliability Vocabulary: SLAs, SLOs, SLIs, and Error Budgets
  3. 03Chapter 3: Redundancy and Fault Tolerance: Eliminating Single Points of Failure
  4. 04Chapter 4: Horizontal vs. Vertical Scaling, and Load Balancing/Auto-Scaling
  5. 05Chapter 5: Caching for Scale: Cache Stampede and Invalidation at Scale
  6. 06Chapter 6: Database Scaling: Replication, Partitioning, Read Replicas, Connection Pooling
  7. 07Chapter 7: Resilience Patterns: Timeouts, Retries, Circuit Breakers, Bulkheads, Load Shedding
  8. 08Chapter 8: Observability: Metrics, Logging, Distributed Tracing, and Actionable Alerts
  9. 09Chapter 9: Incident Response: Severity, Incident Command, Communication, Blameless Postmortems
  10. 10Chapter 10: Chaos Engineering: Proactively Testing for Failure
  11. 11Chapter 11: Capacity Planning and Load Testing
  12. 12Chapter 12: Core Trade-offs: Consistency vs. Availability, Cost vs. Redundancy, Simplicity vs. Resilience
  13. 13Chapter 13: Incident Case Study: The Cascading Failure
  14. 14Chapter 14: Incident Case Study: The Silent Data Corruption
  15. 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? → OrderService called it synchronously, on the critical path, even though recommendations aren't required for checkout to succeed.
  • Why did RecommendationService's slowness affect InventoryService, 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 RecommendationService becomes 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

  1. 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.
  2. Not recognizing the shared connection pool as the actual mechanism of cascade, focusing entirely on why RecommendationService was slow rather than why its slowness was able to affect unrelated, healthy services.
  3. 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.
  4. 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.