Fault Tolerance and High Availability
Components fail all the time; users shouldn't notice. Compare active-passive failover with active-active redundancy, hunt for single points of failure layer by layer, and calculate what each extra 'nine' of availability really allows.
🎯 What you'll learn
- Fault tolerance (mechanism) vs high availability (outcome)
- Active-passive vs active-active redundancy
- Health checks and eliminating single points of failure
- The nines, and graceful degradation of non-critical features
📚 Before you start
1Two Related but Distinct Goals
Fault tolerance is a system's ability to keep functioning correctly when a component fails — a server crashes, a disk dies, a process hangs — without that failure becoming a user-visible failure. High availability (HA) is the resulting property: the system, as a whole, stays reachable and responsive a very high percentage of the time. Fault tolerance is the mechanism; high availability is the outcome it produces. A system can be fault-tolerant for some failure modes and not others — the discipline here is being explicit about exactly which failures you've actually designed for.
2Redundancy: The Foundation of Fault Tolerance
You cannot tolerate the failure of a component you only have one of. Redundancy means running more than one instance of a component so that one failing doesn't take the whole capability down with it — but how those redundant instances are used matters:
Active-passive (a.k.a. primary-standby): one instance handles all traffic; one or more standbys sit idle, ready to take over.
[Primary: handling all traffic] --- replicating state ---> [Standby: idle, up to date]
Primary fails -> Standby is PROMOTED to primary -> traffic redirected to itThe standby does no useful work most of the time — it exists purely as insurance — and the failover (detecting the failure + promoting the standby + redirecting traffic) takes some nonzero time, during which the system is genuinely down.
Active-active: multiple instances all handle traffic simultaneously; if one fails, the others simply absorb its share of load.
[Instance A: handling 1/3 of traffic]
[Instance B: handling 1/3 of traffic] <- load balanced across all three
[Instance C: handling 1/3 of traffic]
Instance B fails -> load balancer stops routing to it -> A and C absorb B's share
(no "promotion" step -- A and C were ALREADY fully capable of serving traffic)Active-active avoids the idle-capacity waste of active-passive and typically fails over faster (no promotion step, just "stop sending traffic to the dead one"), but it requires every active instance to be capable of handling the full range of traffic correctly at all times, and — for anything stateful — requires that state to actually be shared or replicated in a way multiple simultaneously-active writers can use safely (see the CAP theorem topic for what that tradeoff costs).
3Detecting Failure: Health Checks
Redundancy only helps if the system actually notices a component has failed and reacts — otherwise traffic keeps flowing to a dead or broken instance regardless of how many healthy replicas exist elsewhere. A health check is a periodic, automated probe:
Load balancer, every 5 seconds, to each backend instance:
GET /health -> expects 200 OK within 2 seconds
3 consecutive failures -> mark instance UNHEALTHY -> stop routing traffic to it
2 consecutive successes (after being unhealthy) -> mark HEALTHY again -> resume routingThe "N consecutive failures/successes" thresholds exist deliberately, not arbitrarily: a health check that reacts to a single failed probe is too twitchy (one dropped packet or one slow GC pause takes an otherwise-healthy instance out of rotation), while one that takes too long to react leaves traffic flowing to a genuinely dead instance for longer than necessary. A meaningful health check endpoint also needs to actually verify the instance CAN serve real traffic (e.g. check it can reach its own database connection pool), not just that the process is alive and listening — a shallow "always return 200" health check defeats the entire purpose.
4Eliminating Single Points of Failure (SPOFs)
A single point of failure is any one component whose failure takes down the whole system, regardless of how redundant everything else is. Redundancy at one layer is worthless if a different, non-redundant layer sits underneath or beside it:
[3x redundant web servers] -> [1 load balancer] -> the load balancer IS a SPOF
[3x redundant web servers] -> [2x redundant load balancers, active-passive] -> [1 database] -> the database IS a SPOFEliminating a SPOF means applying the same redundancy thinking to EVERY layer a request passes through, not just the layer that's easiest to scale horizontally (typically the stateless application tier) — the load balancer itself, DNS, the database, shared caches, and even things like a single power supply or a single availability zone/datacenter are all candidate SPOFs in a system that hasn't explicitly addressed them.
5Availability Math: The Nines
Availability is usually expressed as a percentage of time a system is genuinely reachable and correct, over a period (commonly a year):
| Availability | Downtime per year | Downtime per month |
|---|---|---|
| 99% ("two nines") | ~3.65 days | ~7.3 hours |
| 99.9% ("three nines") | ~8.76 hours | ~43.8 minutes |
| 99.99% ("four nines") | ~52.6 minutes | ~4.4 minutes |
| 99.999% ("five nines") | ~5.26 minutes | ~26 seconds |
Each additional nine is roughly an order-of-magnitude reduction in acceptable downtime, and in practice an order-of-magnitude increase in engineering cost and complexity to achieve — the jump from three nines to four nines typically requires eliminating SPOFs that were "good enough" at three nines, faster automated failover instead of manual intervention, and often multi-region (not just multi-instance) redundancy. Chasing more nines than a system's actual business requirements justify is a common and expensive mistake; five-nines infrastructure for an internal admin tool used by 10 employees is effort spent on the wrong problem.
6Graceful Degradation
Not every failure has to mean full unavailability. Graceful degradation means a system keeps serving its core functionality even when a non-critical dependency has failed, rather than treating every failure as total:
E-commerce product page, "recommended items" service is DOWN:
Bad: the whole product page fails to load (recommendations became a SPOF
for a feature that isn't actually essential to buying the product)
Good: the product page loads normally, WITHOUT a recommendations section
(the core purchase flow is entirely unaffected by that dependency's failure)This connects directly to earlier topics: a circuit breaker that's open for the recommendations service should fail fast and let the page render without that section, rather than the whole page request hanging or erroring because one non-essential dependency is down — bulkheading (previous topic) is often exactly what keeps that failure from consuming resources needed by the actually-critical parts of the request.
Recommendations as a hard dependency
const [product, recs] = await Promise.all([
getProduct(id), getRecommendations(id) // recs down → whole page 500
]);Optional feature degrades
const product = await getProduct(id);
const recs = await recsBreaker.call(() => getRecommendations(id))
.catch(() => []); // page renders without the section⚠️ Common Pitfalls
- Redundancy at only one layer — three redundant application servers behind one non-redundant load balancer or one non-redundant database just relocates the SPOF, it doesn't eliminate it (Section 4).
- A health check that only verifies the process is running, not that it can actually serve real traffic — keeps routing to an instance that's "alive" but functionally broken (e.g. its database connection pool is exhausted).
- Health check thresholds set too aggressively — reacting to a single failed probe flaps healthy instances in and out of rotation on ordinary jitter, which can itself cause instability.
- Treating every dependency as equally critical — a non-essential dependency (recommendations, "recently viewed") being allowed to take down an entire page defeats the purpose of having redundancy anywhere else; graceful degradation requires explicitly deciding what's core vs optional.
- Chasing availability nines disconnected from actual business need — five-nines engineering effort applied to a system where three nines would be entirely sufficient is cost without corresponding value.
📋 Quick Reference
Fault tolerance = mechanism (surviving a component failure without a user-visible outage)
High availability = the resulting outcome (system stays reachable a high % of the time)
Active-passive: standby idle until promoted -- failover has a detection+promotion gap
Active-active: all instances live, survivors just absorb the dead one's share -- faster failover
Health checks: N-consecutive-failures/successes thresholds; must verify REAL serving capability
SPOF = ANY single component whose failure takes down the whole system -- check every layer
Nines: 99% ~ 3.65 days/yr down | 99.9% ~ 8.76 hr/yr | 99.99% ~ 52.6 min/yr | 99.999% ~ 5.26 min/yr
Graceful degradation: a non-critical dependency failing should degrade a FEATURE, not the
whole system -- pairs directly with circuit breaking + bulkheading🧠 Key takeaways
- Fault tolerance is the mechanism; high availability is the outcome.
- Active-passive wastes idle capacity and has a failover gap; active-active fails over almost instantly but needs headroom.
- Hunt SPOFs at every layer: load balancers, DNS, databases, caches, even power and zones.
- Each extra nine is ~10× less downtime and much more cost. Degrade gracefully: non-critical features fail alone.
🎤 Interview Q&A
Q1How do you calculate availability and what do the nines mean?core▾
Availability = uptime / total time. 99.9% allows ~43 minutes of downtime per month, 99.99% ~4.3 minutes, 99.999% ~26 seconds. Components in series multiply (two 99.9% services ≈ 99.8%); redundant components in parallel improve it (1 − (1 − a)²).
- Why does that matter in design? Long synchronous call chains erode availability; redundancy restores it.
Q2What techniques make a system highly available?core▾
Eliminate single points of failure with redundancy across failure domains (instances, zones, regions), automatic failover with health checks, replication of data, graceful degradation, and fast detection and recovery (low MTTR).
- Active-active vs active-passive? Active-active uses all capacity and fails over instantly; active-passive is simpler but has failover time and idle cost.
Q3What is split brain and how do you prevent it?senior▾
During a partition, two nodes each believe they're the leader and accept conflicting writes. Prevent it with quorum-based leader election (majority of an odd number of nodes), fencing tokens, and STONITH (shutting down the old leader).
- Why odd numbers of nodes? A majority is always well defined; 3 nodes tolerate 1 failure, 5 tolerate 2.
Q4What's the difference between fault tolerance and high availability?core▾
High availability minimizes downtime, possibly with a brief failover interruption. Fault tolerance means continuing without interruption despite a failure (e.g. redundant hardware in lockstep). Most web systems aim for HA plus graceful degradation.
- Which is more expensive? Full fault tolerance, often by a lot.
Q5How do you test that failover actually works?core▾
Regular game days and chaos engineering: kill instances, zones or dependencies in controlled experiments, measure recovery time against the RTO, and fix what breaks. Untested failover usually fails when needed.
- Example tool? Chaos Monkey, AWS Fault Injection Simulator, Gremlin.
🛠️ 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.