Vasu Patel logoVasu Patel
← Designing at Scale: System Design
All chapters in this track
  1. 01Chapter 1: Why System Design Is Its Own Skill
  2. 02Chapter 2: Core Building Blocks and Vocabulary
  3. 03Chapter 3: Eliciting Non-Functional Requirements
  4. 04Chapter 4: Back-of-Envelope Capacity Estimation
  5. 05Chapter 5: Caching Strategies and Cache Invalidation at the Systems Level
  6. 06Chapter 6: Load Balancing and Horizontal Scaling
  7. 07Chapter 7: Database Selection, Replication, and Sharding at a Systems Level
  8. 08Chapter 8: Asynchronous Processing, Message Queues, and Event-Driven Design
  9. 09Chapter 9: API Design from a Systems Perspective: REST, gRPC, GraphQL
  10. 10Chapter 10: The CAP Theorem and Consistency Models in Practical Terms
  11. 11Chapter 11: Microservices vs. Monoliths and Drawing Service Boundaries
  12. 12Chapter 12: Designing for Failure: Redundancy, Failover, Graceful Degradation
  13. 13Chapter 13: A Repeatable Framework for Any System Design Problem
  14. 14Chapter 14: Worked Example: URL Shortener
  15. 15Chapter 15: Worked Example: Chat System
  16. 16Chapter 16: Worked Example: News Feed
  17. 17Chapter 17: Worked Example: Rate Limiter

Chapter 12 of 17

Chapter 12: Designing for Failure: Redundancy, Failover, Graceful Degradation

Why this matters

Every component covered so far in this book — servers, databases, network links, caches — will fail eventually; this isn't pessimism, it's a statistical certainty at real scale (with enough components running for long enough, individual failures become a "when," not an "if"). Designing for failure explicitly, rather than assuming components stay healthy, is what separates a system that degrades gracefully from one that experiences a complete, cascading outage from a single failed part.

Redundancy: eliminating single points of failure

[BEGINNER] A single point of failure (SPOF) is any component whose failure takes down the entire system — the most fundamental failure-design principle is identifying and eliminating these wherever the cost of doing so is justified by the component's actual criticality.

SPOF example: one database server, one load balancer instance, one
              specific server handling a critical function with no
              backup.

Redundancy fix: multiple database replicas (Chapter 7) with automatic
                failover, multiple load balancer instances (often behind
                a DNS-based or cloud-provider-managed redundancy layer),
                multiple instances of every critical service (Chapter 6's
                horizontal scaling, which ALSO provides redundancy as a
                side effect of having multiple instances for capacity reasons).

[SENIOR INSIGHT] Redundancy has a real, deliberate cost (running duplicate infrastructure that mostly sits idle, ready to take over) — this is precisely why identifying SPOFs and eliminating them should be prioritized by actual criticality and probability, not applied uniformly to every component regardless of its actual failure impact. A component whose failure only affects a rarely-used administrative feature doesn't warrant the same redundancy investment as the primary database or the authentication service.

Failover: automatically routing around a failed component

[BEGINNER] Failover is the mechanism that actually uses redundancy when a failure occurs — automatically detecting a failed component and routing traffic/responsibility to a healthy replacement, ideally with minimal or no manual intervention and minimal user-visible disruption.

Database failover: primary fails -> a replica (Chapter 7) is automatically
                    promoted to primary -> application configuration
                    updates to point at the new primary -> service resumes,
                    typically with a brief interruption during the
                    detection-and-promotion window.

Load balancer / server failover: a health check (Chapter 6) detects a
                    failed instance -> the load balancer stops routing
                    traffic to it -> remaining healthy instances absorb
                    the load -> (separately) a new instance is provisioned
                    to replace the failed one.

[IN PRACTICE] Failover is never instantaneous — there's always a detection window (how long until the health check notices the failure) and a promotion/rerouting window (how long until traffic actually flows to the replacement) — this real, non-zero gap is exactly why a stated availability target (Chapter 3's "how many nines") needs to account for failover speed specifically, not just redundancy's mere existence: redundant infrastructure that takes ten minutes to fail over doesn't achieve a five-nines availability target on its own, regardless of how many replicas exist.

Graceful degradation: staying partially functional instead of fully failing

[SENIOR INSIGHT] Graceful degradation is designing a system so that when one component fails, the overall system continues operating with reduced functionality, rather than the failure of one part taking down the whole system — directly the companion OOP book's Chapter 14 point about single points of failure in a design, and the companion Coding Standards book's Chapter 5 fail-fast-vs-fail-gracefully distinction, both applied at the whole-system architecture scale.

Without graceful degradation: the recommendation service fails ->
the ENTIRE product page fails to load, because the page's rendering
logic has a hard dependency on recommendations being present.

With graceful degradation: the recommendation service fails -> the
product page loads normally, simply WITHOUT the recommendations
section (or with a cached, slightly-stale version) -- the core
functionality (viewing and buying the product) remains fully available
despite a real, ongoing failure elsewhere in the system.

The design principle this requires: identifying which pieces of functionality are truly core (must work, or the whole request should fail — Chapter 8's synchronous-path test) versus genuinely optional enhancements (should degrade gracefully if unavailable) — and architecting each dependency's failure handling according to that classification, explicitly, rather than treating every dependency as equally critical by default.

The circuit breaker: preventing a failing dependency from cascading

[IN PRACTICE] Directly the companion Networking book's Chapter 11 pattern, applied at the systems-design level: a circuit breaker stops sending requests to a dependency that's already failing, for a cooldown period, rather than continuing to send requests (and continuing to consume resources waiting for them to time out) to something that's clearly not going to respond successfully — this prevents one failing, slow dependency from consuming so many resources (threads, connections) in the calling service that the calling service itself becomes unresponsive, turning one component's failure into a cascading failure across the whole system.

Common mistakes

  1. Not identifying single points of failure explicitly during design, discovering them only when one actually fails in production and takes down more of the system than necessary.
  2. Applying uniform redundancy investment regardless of a component's actual criticality, over-investing in redundancy for low-impact components while potentially under-investing in genuinely critical ones.
  3. Treating every dependency as equally critical, missing the graceful-degradation opportunity where a non-core dependency's failure could be isolated rather than taking down an entire user-facing flow.
  4. Not accounting for failover's real detection-and-promotion time gap when stating an availability target — redundancy alone doesn't achieve a specific "nines" target; the speed of failover matters just as much.

Discussion / interview questions

  1. "What is a single point of failure, and how would you identify and address one in a system you're designing?" Strong answer: Any component whose failure takes down the entire system; addressed via redundancy (multiple instances/replicas) and automatic failover — prioritized by the component's actual criticality and failure probability, not applied uniformly regardless of impact.

  2. "Why isn't redundancy alone sufficient to guarantee high availability?" Strong answer: Failover isn't instantaneous — there's a real detection window (noticing the failure) and a promotion/rerouting window (redirecting traffic to the replacement); an availability target needs to account for this real gap, not just the mere existence of redundant infrastructure.

  3. "What is graceful degradation, and how would you design for it in a system with an optional recommendation feature?" Strong answer: Designing so a non-core dependency's failure doesn't take down the whole request — for a recommendation feature specifically, the product page should still load and function fully (viewing, buying) even if recommendations are unavailable, simply omitting or showing a cached version of that one section instead of failing the entire page load.

  4. "What is a circuit breaker, and what specific failure mode does it prevent?" Strong answer: Stops sending requests to an already-failing dependency for a cooldown period rather than continuing to send requests that will likely fail/time out — preventing the calling service from exhausting its own resources (threads, connections) waiting on a dependency that isn't going to respond, which would otherwise turn one component's failure into a cascading failure across the calling service too.

Chapter summary / cheat sheet

  • Identify and eliminate single points of failure via redundancy, prioritized by actual criticality — not applied uniformly regardless of impact.
  • Failover (automatically routing around a failed component) has a real, non-zero detection-and-promotion time gap that must be accounted for when stating an availability target.
  • Graceful degradation lets a system stay partially functional when a non-core dependency fails, rather than the failure taking down an entire request — requires explicitly classifying dependencies as core versus optional.
  • A circuit breaker prevents a failing dependency from cascading into the calling service's own failure, by stopping wasted requests to something that's clearly not going to respond successfully.