Reliability & Disaster Recovery

High Availability

The standard shape: one region, an application spread across two zones, a load balancer in front. What it protects against, what it does not, and why database redundancy is a separate problem with a separate answer.

The question this answers

Infrastructure question

What is the smallest topology that survives losing a host or a zone without a customer noticing?

Application requirement

The API must keep serving through a host failure, a rolling deploy and a zone-level incident. Users must not see errors during any of these, and nobody should have to be woken up for the first two.

What it provides

Continuous service across the loss of any single instance or any single zone, with detection and rerouting handled automatically by the load balancer rather than by a person.

Application RequirementInfrastructure RequirementComputeNetworkStorageIdentityDeploymentScalingReliabilityObservabilitySecurityCostTrade-offs

The canonical shape, and what each part is for

The topology is deliberately boring: a load balancer with targets in two zones, application instances that hold no state, and a database whose redundancy is arranged separately. Every element is there for a named reason. The load balancer provides one stable address plus health-based routing, so a failed instance stops receiving traffic without a DNS change. Two zones mean a facility-level event removes half the capacity rather than all of it. Statelessness is what makes any instance interchangeable with any other, which is the property everything above depends on.

Note the capacity arithmetic, because it is where this design is most often wrong. If each zone runs exactly half the fleet and one zone is lost, the survivors must absorb 100% more traffic. Autoscaling will not save you — new capacity is minutes away (Startup Time & Cold Start) and the surviving zone may be exactly where capacity is scarce during a regional event. Either run each zone at under 50% utilization, or accept degradation during a zone failure and say so.

Database redundancy is handled separately, and deliberately so. The application tier is stateless and therefore easy: add instances, remove instances, nothing is lost. The database is the opposite: it has one writer, its failover takes tens of seconds, it drops every in-flight connection when it moves, and its replication mode is a durability-versus-latency decision. Drawing it as another box behind the load balancer hides all of that. It is covered in Managed Databases and in the Database Engineering domain, and it is usually the component that actually determines your availability number.

Application HA in one region. The database is redundant by a different mechanism.PROVIDER-NEUTRAL
Clientspublic
Region eu-1
Load balancer (zone-redundant)public— public on 443 — the design, not a finding
Zone A
app instances (stateless)private— must run under 50% to absorb zone B
Database primaryprivate— separate mechanism: replication + promotion, not load balancing
Zone B
app instances (stateless)private— must run under 50% to absorb zone A
Database standbyprivate— synchronous replica; promotion drops all connections
Shared session / cache storeprivate— the thing that makes the app tier stateless
ClientsLoad balancer (zone-redundant)· HTTPScrosses boundary
Load balancer (zone-redundant)app instances (stateless)· health-based routing
Load balancer (zone-redundant)app instances (stateless)· health-based routing
app instances (stateless)Database primary
app instances (stateless)Database primary· cross-zone
Database primaryDatabase standby· replication
app instances (stateless)Shared session / cache store
app instances (stateless)Shared session / cache store

What "highly available" actually claims

Availability numbers are quoted constantly and understood rarely. Three nines is nearly nine hours of downtime a year; four nines is 52 minutes; five nines is about five minutes — less time than most database failovers plus detection. Before promising a number, work out whether any single planned event in your year already exceeds it.

Two subtleties matter more than the arithmetic. First, availability composes multiplicatively across dependencies in series: a service at 99.9% that requires a database at 99.9% and an identity provider at 99.9% cannot itself exceed about 99.7%. Adding dependencies lowers your ceiling, which is a strong argument for degrading gracefully when a non-essential dependency is unavailable.

Second, the number that matters is measured from the user, not from the component. A load balancer that reports 100% healthy targets while every request returns a 500 has excellent component availability and zero service availability. Measure at the edge, on real requests — the depth of that argument belongs to the Observability & Performance domain.

TargetDowntime per yearDowntime per monthWhat that budget must cover
99% ("two nines")~3.65 days~7.2 hoursComfortable. A manual restore fits inside this.
99.9%~8.8 hours~43 minutesOne long incident a year, or a monthly maintenance window. Single-zone with a standby can reach this.
99.95%~4.4 hours~22 minutesMulti-zone with automatic failover. Deploys must be zero-downtime.
99.99%~52 minutes~4.3 minutesEvery failover must be automatic and fast. One bad deploy consumes the year.
99.999%~5 minutes~26 secondsLess than one database failover. Requires multi-region active-active and a large organization to run it (Active-Active).
Availability targets and what they leave room for

Statelessness is the prerequisite, not a nice-to-have

Everything above assumes any instance can serve any request. The moment an instance holds something a request needs — a session in local memory, an uploaded file on local disk, a counter in a process variable — the load balancer can no longer route freely, and the usual workaround is sticky sessions. Sticky sessions convert an instance failure from "invisible" into "these users are logged out", and they undermine the property the whole design rests on.

The fix is to move state to something shared and redundant: sessions to a shared store, uploads to object storage, counters to the database or cache. It is a small amount of work and it is what makes instances disposable — which is also what makes rolling deploys, autoscaling and self-healing possible. See Persistent Data and Containers and Stateful Workloads: Databases Are Not Stateless APIs.

The pair below is the actual difference, in the only place it shows up: where a request's state lives.

State in the process — the load balancer now has to care which instance you reach
const sessions = new Map<string, Session>()   // lives in this process

app.post('/login', (req, res) => {
  const id = randomId()
  sessions.set(id, { userId: req.user.id, cart: [] })
  res.cookie('sid', id)
})

// Consequences: sticky sessions required; an instance failure logs
// those users out; a rolling deploy logs everyone out; scale-in
// silently discards carts.
State in a shared, redundant store — every instance is interchangeable
app.post('/login', async (req, res) => {
  const id = randomId()
  await sessionStore.set(id, { userId: req.user.id }, { ttl: 3600 })
  res.cookie('sid', id)
})

// Consequences: any instance serves any request; instance loss is
// invisible; rolling deploys and scale-in are uneventful. The cost
// is one network hop per request and a new dependency that must
// itself be redundant.

High availability at the application tier is a consequence of instances being interchangeable. Any state kept in a process is a constraint on routing, and every constraint on routing is a hole in the availability story. The trade is real: you have added a hop and a dependency that now appears in your availability multiplication.

Key points

  • The canonical HA shape is one region, two or more zones, stateless instances, a health-checking load balancer in front.
  • Database redundancy is a separate mechanism — replication and promotion, not load balancing — and it usually sets your real availability ceiling.
  • N-1 capacity must be real: if losing a zone doubles the load on survivors, they need that headroom before the failure, not after.
  • Availability composes multiplicatively across serial dependencies; every added dependency lowers the ceiling.
  • Statelessness is the prerequisite. Sticky sessions turn an invisible instance failure into a user-visible one.
  • Measure availability at the edge on real requests. Component health is not service health.

The loop, answered

Every field is required, which is why no lesson here can recommend something without saying what it costs and what simpler thing to consider first.

How it works
  • The load balancer publishes one stable address and distributes requests across registered targets in multiple zones.
  • Health checks remove failed targets automatically, so an instance or host failure costs a detection window rather than an outage (Health Checks).
  • Instances hold no request-scoped state, so any of them can serve any request and replacement is invisible.
  • The database maintains a standby in another zone via replication; failover promotes it and clients reconnect.
  • Shared state — sessions, cache, uploads — lives in redundant services outside the instances.
What you still own
  • Verify zone distribution continuously. Schedulers drift, and a fleet that quietly became single-zone looks identical on a dashboard.
  • Keep enough headroom to lose a zone. This is the least popular and most important operational fact in this lesson.
  • Test failover on purpose: drain a zone during business hours and watch what actually happens (Failure Domains).
  • Own connection behaviour across database failover — pools must reconnect, and the application must handle in-flight query errors rather than surfacing them.
  • Keep deploys zero-downtime, because in a 99.99% budget one careless rollout consumes the year (Rolling Deployment and the Compatibility It Demands).
How it fails
  • Both zones lose capacity at once because the scheduler placed everything in one and nobody checked.
  • Zone failure exceeds surviving capacity: the survivors saturate and the partial failure becomes total.
  • Database failover drops every connection; applications that do not reconnect cleanly stay broken after the database is healthy.
  • Sticky sessions mean an instance failure logs out a subset of users while every infrastructure metric stays green.
  • The load balancer itself is single-zone on some configurations — worth verifying rather than assuming.
  • A shared dependency (config service, identity provider, secret store) is single-zone, so zone loss takes everything regardless of how the app is spread.
How it scales
  • The stateless tier scales horizontally; the database does not, and remains the ceiling until you shard or split reads (Managed Databases).
  • Three zones is cheaper per unit of resilience than two: losing one of three costs 33% headroom rather than 50%.
  • Cross-zone traffic grows with fleet size and becomes a real cost and latency term (Multi-Zone Deployment).
  • Beyond a certain size the shared session or cache store becomes the new single point of failure and needs its own redundancy story.
Security
  • The load balancer is the intended public surface and terminates TLS. A public listener on 443 is the design; the finding would be an instance or database reachable directly.
  • Instances sit in private subnets and accept traffic only from the load balancer's security group (Security Groups: The Stateful Firewall).
  • The shared session store now holds authentication material for every user — it is a high-value target and needs encryption, network isolation and tight access control.
  • Zone redundancy does not change the trust boundary: the same identity and network controls must apply identically in every zone, and drift between them is a real finding.
Cost shape
  • Roughly a doubling of the application tier compared with a single instance, plus headroom that is idle by design.
  • A synchronous standby database is a second full-price database that serves no read traffic in most configurations.
  • Cross-zone data transfer is metered and grows with chattiness between tiers.
  • The load balancer itself has an hourly charge plus a throughput or connection-based meter.
What to watch
  • Healthy target count per zone, not just in total — a zero in one column is the signal that matters.
  • Utilization per zone against the N-1 threshold, so headroom is a monitored fact rather than an assumption.
  • Database failover events, their duration, and application error rates during them.
  • Availability measured at the edge from real requests, compared against the component-level view.
  • The signal that lies: overall CPU across the fleet. It looks comfortable right up to the moment one zone is carrying everything.
Simpler alternatives
  • A single instance with a fast, tested restore. For an internal service where 30 minutes of downtime is acceptable, this is dramatically simpler and cheaper, and the honest answer more often than teams admit.
  • Two instances in one zone. Removes host failure — the most common cause — for half the complexity of multi-zone, if a zone event is a risk you accept.
  • A managed platform that provides zone redundancy by default, so you buy tested failover instead of building it.
  • Vertical scaling with a standby, for workloads that genuinely cannot run twice. The availability story is worse and it is achievable, whereas a broken horizontal design is not.
What adopting this costs
  • Buys survival of host and zone failure; costs roughly double compute, permanent idle headroom and cross-zone transfer.
  • Statelessness enables everything here and adds a network hop plus a dependency to every request that touches session state.
  • Automatic failover removes human latency and can trigger for the wrong reason, moving traffic during a false positive.
  • Multi-zone protects against a facility event and does nothing against a bad deploy, an expired certificate or a regional control-plane failure.

What people believe, and what is true

Claim

Multi-zone means the database is highly available too.

Reality

The database needs replication and promotion, which is a different mechanism with its own failover time and its own data-loss window.

Claim

Autoscaling covers the capacity gap when a zone fails.

Reality

New capacity takes minutes and may be unavailable in the surviving zone precisely during a regional event. Headroom must exist beforehand.

Claim

Two zones is twice as available as one.

Reality

It removes one failure class. Deploys, certificates, quotas, dependencies and control-plane events are unaffected, and those cause most outages.

Apply it