Filter concepts by levelShowing all levels.

System Design · High Availability

High Availability

Removing single points of failure, choosing a failover topology, and making redundancy actually work during a real failure

  • No SPOFsevery layer, not just app instances
  • Active-activeall nodes serve now, no promotion step
  • Active-passivestandby promoted on failure
  • Health checks + failoverdetect, then react, without a human

1.Removing single points of failure across every layer

A single point of failure (SPOF) is any one component whose failure alone takes the whole system down. High availability is not a property of one clever component — it is the result of walking every layer a request passes through (load balancer, application instances, database, cache, queue, storage, network) and making sure each one individually survives losing an instance, a zone, or a link. Missing even one layer means the system's real availability is capped at that one un-redundant component, no matter how redundant everything else is.

Key features

  • A SPOF is defined by blast radius, not by rarity — a component that fails once a decade but takes the whole system down when it does is still a SPOF
  • Each of the seven layers needs its own redundancy technique — there is no single fix that covers load balancer, database, and network all at once
  • Databases split into two different SPOF problems: losing the primary (needs a replica that can be promoted) and losing a whole zone or region (needs cross-zone/region replication)
  • Networking SPOFs are the easiest to forget — a single NIC, a single top-of-rack switch, or a single availability zone's network path can undo redundancy at every other layer
  • Removing a SPOF always costs something — more instances, cross-zone replication lag, or operational complexity — so the checklist is a prioritization tool, not an obligation to make every layer maximally redundant everywhere

Layer-by-layer SPOF elimination across the request path

Layer-by-layer SPOF elimination across the request path
LayerCommon SPOFRedundancy technique
Load balancerOne LB instance or one AZ hosting itRedundant LB pair (active-active or active-passive) with a floating/virtual IP or DNS-based failover
Application instancesToo few instances, or all in one AZMultiple stateless instances spread across at least two availability zones
DatabaseSingle primary with no replicaPrimary plus one or more replicas, with automated or manual failover promotion
CacheSingle cache node holding all keysClustered cache (e.g. replicated or sharded) so one node's loss does not evict the whole cache
Queue / brokerSingle broker node or single partition leaderClustered broker with replicated partitions and leader election on node loss
StorageSingle disk or single-zone bucketReplicated block storage (e.g. RAID or a distributed filesystem) or a multi-zone object store
NetworkingSingle NIC, switch, or availability zone network pathRedundant network paths and multi-AZ deployment so no single network element is load-bearing
Every layer on the request path needs its own redundancy

Load balancer

redundant pair, not one instance

Application instances

spread across multiple AZs

Database

primary + replica(s), automated failover

Cache

clustered, not one node

Queue / broker

replicated partitions

Storage

replicated disks or multi-zone object store

Network

redundant paths, no single switch or NIC

  1. Load balancer — redundant pair, not one instance
  2. Application instances — spread across multiple AZs
  3. Database — primary + replica(s), automated failover
  4. Cache — clustered, not one node
  5. Queue / broker — replicated partitions
  6. Storage — replicated disks or multi-zone object store
  7. Network — redundant paths, no single switch or NIC

Command

text
for each layer in [load_balancer, app_instances, database,
                    cache, queue, storage, network]:
    ask: "if exactly this one thing fails right now,
          does the system stay up?"
    if no: it is a SPOF -- add redundancy or accept the risk explicitly

overall availability ~= availability of the LEAST
redundant layer in the path

High availability means walking all seven layers explicitly -- load balancer, application instances, database, cache, queue, storage, network -- and giving each one its own redundancy answer. Fixing the layers that are easy to remember while skipping cache, queue, storage or networking leaves a real SPOF hiding behind a system that otherwise looks redundant.

2.Active-active vs active-passive

Active-active runs two or more redundant instances (servers, databases, or whole regions) that all serve live traffic at the same time -- if one fails, the others simply absorb its share of load, with no failover step needed. Active-passive keeps one instance serving traffic while one or more standby instances sit idle (or replicating data) until the active one fails, at which point a failover promotes a standby to active. The choice trades cost and consistency complexity against failover speed and wasted capacity.

Key features

  • Active-active has no failover delay for reads or writes routed to a surviving node -- capacity that was already serving traffic simply absorbs more
  • Active-passive wastes standby capacity between failovers, but sidesteps the hardest problem in active-active: keeping multiple simultaneously-written copies of data consistent
  • Active-active for a database usually means either read replicas serving reads (writes still go to one primary) or a genuinely multi-master setup that must resolve write conflicts
  • Active-passive failover is not instant -- it requires detecting the failure, then promoting a standby, and that promotion window is exactly the availability gap active-active avoids
  • The choice is rarely all-or-nothing across a whole system -- one component can run active-active (stateless app servers) while another right next to it runs active-passive (a single-writer database)

Active-active vs active-passive trade-offs

Active-active vs active-passive trade-offs
DimensionActive-activeActive-passive
Cost / resource utilizationHigher -- all nodes run live capacity, none sits idleLower -- standby capacity sits mostly idle until failover
Failover speedEffectively instant -- no promotion step, traffic just redistributesSlower -- failure must be detected, then a standby promoted before it can serve
Data-consistency complexityHigh -- concurrent writes to multiple nodes need conflict resolution or partitioningLow -- one writer at a time, so no concurrent-write conflicts to resolve
Best fitStateless services, read-heavy data tiers, systems that can tolerate eventual consistencySingle-writer systems (most relational databases), or anything where correctness matters more than failover speed
Active-active vs active-passive on failure

Active-active

  • Both nodes serve live traffic simultaneously
  • One node fails -> the other already has capacity, no promotion needed
  • Requires resolving concurrent writes across nodes

Active-passive

  • Only the active node serves traffic; standby stays idle or replicating
  • Active fails -> detect failure, then promote standby -> traffic resumes
  • Only one writer at a time, so no concurrent-write conflicts
  • Active-active
    • Both nodes serve live traffic simultaneously
    • One node fails -> the other already has capacity, no promotion needed
    • Requires resolving concurrent writes across nodes
  • Active-passive
    • Only the active node serves traffic; standby stays idle or replicating
    • Active fails -> detect failure, then promote standby -> traffic resumes
    • Only one writer at a time, so no concurrent-write conflicts

Command

text
active-active:
  node_a.serves(traffic)  # both serving now
  node_b.serves(traffic)
  # node_a fails -> node_b already absorbing load, no promotion step

active-passive:
  active.serves(traffic)
  standby.replicate(active)  # idle for traffic, staying in sync
  # active fails -> detect failure -> promote(standby) -> standby.serves(traffic)

Active-active removes failover delay by keeping every node serving live traffic, at the cost of having to resolve concurrent writes across nodes. Active-passive avoids that consistency problem by keeping one writer at a time, at the cost of a failover gap while a standby gets detected and promoted. Neither is universally better -- match the topology to whether the component can safely accept concurrent writes at all.

3.Health checks, automatic failover and graceful degradation working together

Redundant capacity only helps during a real failure if something notices the failure and reacts to it -- that is the job of these three mechanics working in sequence. Health checks continuously ask "is this component still working?" so a failure gets detected instead of assumed. Automatic failover reacts to a failed health check by rerouting traffic or promoting a standby without waiting for a human. Graceful degradation is the fallback for the failures automatic failover cannot fully hide -- serving a reduced but still-useful response (cached data, a simplified feature set) instead of a hard error when full functionality genuinely is not available.

Key features

  • A health check is only useful if its failure definition matches real user impact -- a check that only pings a process (not its ability to serve a real request) can report "healthy" while users see errors
  • Automatic failover needs the health check to be right consistently, not just once -- a single flaky check triggering failover causes unnecessary disruption (a false positive failover is itself an outage)
  • Graceful degradation is what runs when failover cannot fully restore capacity -- e.g. a downstream dependency is down and there is no redundant copy of it to fail over to at all
  • These three mechanics form a chain, not independent options: health checks detect, failover reacts, and degradation covers whatever failover could not fix
  • A system with automatic failover but no graceful degradation still fails hard the moment something breaks that failover cannot route around -- degradation is what keeps a partial failure from becoming a total outage for the end user

The three mechanics and what each is responsible for

The three mechanics and what each is responsible for
MechanicQuestion it answersWhat happens if it is missing
Health checksIs this component actually able to serve a real request right now?A dead component keeps receiving traffic because nothing noticed it failed
Automatic failoverNow that a failure is detected, how fast can traffic move away from it without a human?Detection happens, but recovery waits on a human to notice the alert and act -- minutes to hours of extra downtime
Graceful degradationWhen failover cannot fully restore normal service, what is the best partial response?Any failure failover cannot fully route around becomes a hard error for every user, instead of a reduced-but-working response
Detection, reaction, and fallback in sequence
check failsstandby/replicaavailablenothing left tofail over to

Health check

is it really serving requests?

Failure detected

Automatic failover

reroute or promote, no human

Service restored

Graceful degradation

reduced response, not a hard error

  • Health check — is it really serving requests?
    • leads to Failure detected (check fails)
  • Failure detected
    • leads to Automatic failover
  • Automatic failover — reroute or promote, no human
    • leads to Service restored (standby/replica available)
    • on error, leads to Graceful degradation (nothing left to fail over to)
  • Service restored
  • Graceful degradation — reduced response, not a hard error

Command

text
loop every N seconds:
    result = health_check(component)   # detect
    if result == unhealthy:
        automatic_failover(component)  # react without a human

# when even failover cannot restore full service:
def handle_request():
    try:
        return full_response()
    except DependencyUnavailable:
        return degraded_response()  # cached / reduced, not a hard error

Health checks detect a real failure (not just "is the process up"), automatic failover reacts to that detection without waiting for a human, and graceful degradation is the fallback for whatever failover could not fully route around. Skip the first and failover never triggers; skip the last and any failure failover cannot fully absorb becomes a hard outage instead of a reduced-but-working response.

Remember

  • Walk all seven layers explicitly -- load balancer, app instances, database, cache, queue, storage, network -- a system is only as available as its least redundant layer.
  • Active-active removes failover delay but requires resolving concurrent writes; active-passive avoids that at the cost of a detect-then-promote gap.
  • Health checks only help if they exercise a real dependency, not just "is the process up" -- a shallow check never fires the failover it is supposed to trigger.
  • Graceful degradation is what covers the failures automatic failover cannot fully route around -- without it, a partial outage still becomes a hard error for users.

Pro tip

  • One system can mix topologies -- stateless app servers active-active, a single-writer database active-passive -- the choice is per-component, not all-or-nothing.
  • Measure full failover time end to end (detection + promotion + client reconnect), not just replication lag -- that is the number that actually matters during an incident.

Next up

  • Multi-region systems
  • Disaster recovery
  • Chaos engineering

Sources

  • AWS Well-Architected Reliability Pillar — docs.aws.amazon.com
  • Google Cloud Architecture Framework: Reliability — cloud.google.com
  • Checked 2026-08-25

Spotted a mistake on this page? Tell us.