Multi-Region Systems

Active-Passive: Simple to Reason About, Rarely Tested

One region serves; another stands ready. The model is easy to explain and easy to be correct about — there is exactly one writer, so there are no conflicts, ever. Its two weaknesses are not conceptual: the failover is slow and multi-step, and it is almost never exercised, which means its probability of working the first time is far below what the runbook implies.

▶ Run the lab

The question this answers

The question

A standby region exists. What actually happens when I have to use it, and how long has it been since anyone checked?

The guarantee — the property claimed, and its scope

While the active region is serving: linearizable writes for all keys, no conflicts by construction. During and after a failover: the promoted region contains every write that had replicated before the loss and none that had not — so the guarantee is "no divergence, but a data-loss window equal to the replication lag at the moment of failure" under asynchronous replication, and "no loss, at the cost of an RTT on every write" under synchronous.

Everything below is bought to hold this sentence. "Strongly consistent" with no scope attached is a slogan, not a guarantee — read what it actually covers, and what it explicitly does not.

What a node knows — observation versus inference

The standby knows the last log position it received and how long ago that was. It does not know whether the active region is dead, unreachable, or simply slow — and it cannot know, because those are indistinguishable across a region boundary ([[crash-vs-slow]]). Promotion is therefore always a decision made under uncertainty, and the only sound way to make it safe is to fence the old primary rather than to try harder to determine its state.

A node knows its own state and the messages that arrived. Everything else is inference from evidence that was already stale. "B has not replied in five seconds" is knowledge; "B is down" is a decision — and usually the bug.

What guarantee?What does a node know?How does it work?What can fail?How does it fail?Where is coordination?What holds under failure?How does it recover?How would you know?What is the simpler thing?
active-passivefailoverstandbyrpodisaster recovery

What "passive" actually means, in four flavours

The word hides a spectrum, and the differences dominate your recovery time far more than any architectural subtlety.

A cold standby is data only: backups in the second region, no compute running. Recovery means provisioning everything, which is hours, and it is the honest choice when the business genuinely tolerates hours. A warm standby runs the databases and replicates, but the application tier is scaled to zero or near it. A hot standby runs everything at production scale and receives no user traffic. A hot standby serving reads runs everything and takes read traffic, which is the only variant continuously proving that it works — and is therefore the only one whose failover has a good chance of succeeding.

That last observation is the most useful thing in this lesson. The reliability of a standby is roughly proportional to how much of it is used in normal operation. Everything else is a plan, and plans decay.

FlavourTypical RTOCostWhat is continuously proven
Cold (backups only)typicalHours to days~5% of productionThat backups exist. Not that they restore.
Warm (data replicating, app scaled down)typical15–90 minutes~30%That replication works. Not that the app boots there.
Hot (full scale, no traffic)typical5–20 minutes~100%That processes start. Not that they serve real requests.
Hot, serving readstypical2–10 minutes~100%Capacity, config, certificates, dependencies, and the read path — everything except the write promotion.
The standby spectrum

The failover is not one step. It is seven, in sequence.

The runbook says "fail over to the standby". What happens is a chain, and each link has its own latency, its own failure probability and its own owner — so the total time is the sum, not the maximum, and the total success probability is the product, not the best link.

Write these out with real numbers for your own system and the RTO in the disaster recovery document usually turns out to be a third of the truth. The detection step alone is typically the largest term, because you cannot set it aggressively: a detector fast enough to catch a real outage in thirty seconds is also fast enough to fire on a routine network blip, and a spurious failover is worse than a slow one.

t+0:00   Region EU stops responding to health checks
t+0:30   Detector waits out the flap window          (+30s, tunable, risky both ways)
t+2:00   Human is paged, opens laptop, reads runbook (+90s, unavoidable if not automated)
t+5:00   Decision: is it down, or is it the link?    (+3m, this is the hard one)
t+6:30   Fence: revoke EU's write lease / storage    (+90s, MUST precede promotion)
t+8:00   Promote US replica to primary               (+90s, replication drain first)
t+11:00  Repoint traffic: DNS TTL / global LB         (+3m, TTL is a floor you set months ago)
t+16:00  Cold caches, empty pools, JIT warm-up        (+5m, the "thundering herd on an
                                                       empty cache" phase — often the
                                                       point at which the standby falls over)
t+20:00  Steady state, degraded capacity
------------------------------------------------------------------
runbook RTO:  5 minutes
actual RTO:  20 minutes, on a good day, with a rehearsed team
A failover timeline, with the times nobody writes down

RPO is a number, and it is bigger than you think

Under asynchronous replication, the writes sitting in the replication lag window at the moment of failure are gone. Not delayed — gone, unless the dead region comes back and you reconcile manually. And those writes were acknowledged to users. Someone was told their order was placed.

So state the RPO as a measured quantity: "replication lag p99 is 3.2 seconds, therefore our worst-case data loss is roughly 3.2 seconds of writes, which at peak is about 900 orders." That sentence is worth more than any diagram, because it is the sentence that gets someone to fund synchronous replication or to accept the risk explicitly.

The lag is not constant. It climbs under exactly the conditions that precede failures: a load spike, a bulk import, a slow disk in the standby, a network degradation. The correlation is unhelpfully in the wrong direction — your data loss window is widest at the moment you are most likely to need it. Alert on the lag itself, not on the health of the write path, because the write path will be perfectly healthy the whole time.

Synchronous replication removes the loss and buys it back as latency and availability: every write now waits for the far region, which is the RTT from [[speed-of-light]], and a standby that is merely slow now stalls the primary. [[synchronous-replication]] covers the mechanics; the geo-specific consequence is that at 90–200 ms per write, synchronous cross-region replication is affordable for a ledger and not for a session store, so most systems end up doing both for different data.

Why promotion is a split-brain decision

Promotion is not an administrative act. It is a claim that there is now exactly one primary, and that claim can be false in the one way that matters: the old primary is alive, unreachable from you, and still accepting writes from users who can reach it.

This is why fencing must precede promotion, not follow it. The safe sequence is: revoke the old primary’s ability to commit — by lease expiry, by storage-level token rejection, by taking away the network path or the credential — *and then* promote. If you promote first and fence later, you have created two primaries for the length of the gap, and every write in that gap is a divergence that no automatic mechanism will merge. [[fencing-tokens]] is the general treatment and [[split-brain]] is the failure.

A human in the loop is the usual compromise and a defensible one. It costs minutes of RTO and buys a judgement that no health check can make. What is not defensible is an automatic failover with no fencing, which converts a partition — recoverable — into a divergence — not recoverable. If your automation cannot fence, it should page instead of promoting.

The moment before promotion: what each side believessimplified
eu ↔ us: partitioned — no traffic crossesop ↔ eu: partitioned — no traffic crossesop ↔ us: okEU (primary) · leader · isolated — still serving European users, still committing⦸ EU (primary)★ leaderisolatedUS (standby) · follower · up — last received log entry 47 s agoUS (standby)· followerOn-call operator · observer · up — dashboard for EU is blankOn-call operator◇ observerpartitionedpartitioned
partitionedok
  • EU (primary) — still serving European users, still committing
  • US (standby) — last received log entry 47 s ago
  • On-call operator — dashboard for EU is blank
What each node believes
  • usbelieves “EU is down”✕ and it is false
  • eubelieves “I am still the primary and may commit”✓ and it is true
  • opbelieves “promoting US is safe”✕ and it is false

Every node above is acting on what it believes. Nothing in the cluster tells the mistaken one that it is mistaken.

The part nobody rehearses: failback

The dead region comes back. It has a database that diverged from the promoted one for however long it kept accepting writes, plus whatever it had that never replicated. Now what?

The honest answer for most teams is that there is no procedure, so the recovered region is rebuilt from the new primary and the divergent writes are lost — a second data-loss event, occurring after the incident is declared resolved, and usually not counted in it. Doing better means capturing the old primary’s unreplicated tail before wiping it, and reconciling it as a business exercise, which is [[reconciliation]] and is genuinely slow work.

The other trap is failing back too eagerly. The original region is "the main one" for organisational reasons, so traffic is moved back during business hours, which is a second unrehearsed failover, in the opposite direction, on a region whose state you are not sure of. Failing back is not the reverse of failing over. It deserves its own plan and its own maintenance window, and often the right answer is to stay where you are and rename the regions in the documentation.

Making the standby real

The failure mode of active-passive is not conceptual, it is entropic. The standby drifts: a config change applied to production only, a certificate that expired, an IAM role never created there, a capacity quota that was never raised, a schema migration applied to the primary and not the replica, a secret rotated in one place, a dependency that only whitelisted the primary region’s egress addresses. None of these produce a signal until the failover.

There are exactly two effective countermeasures, and neither is a document. Route real traffic through it — even 5% of reads keeps the config, capacity, certificates and dependency allowlists continuously honest. Fail over on purpose, on a schedule — quarterly, in business hours, announced, with the ability to abort. A failover that has been performed six weeks ago is a routine operation; one that has never been performed is an experiment being run for the first time during an outage. [[chaos-engineering]] and [[fault-injection]] are the disciplines; Cloud’s restore-testing is the same argument applied to backups.

Key points

  • One writer means no conflicts, ever. That is active-passive’s real and substantial advantage.
  • Failover is a sequence of seven or so steps whose times add up; the runbook RTO is typically a third of the measured one.
  • Detection is usually the largest term, and it cannot be tuned aggressively without causing spurious failovers.
  • Under asynchronous replication, RPO equals the replication lag at the moment of failure — and that lag is widest exactly when failures happen.
  • Fencing must precede promotion. Promote-then-fence creates two primaries for the length of the gap.
  • A standby’s reliability is proportional to how much of it is exercised in normal operation.
  • Failback is a separate, harder, unrehearsed operation, and the divergent tail of the old primary is usually quietly discarded.

The chain, answered

Every field here is required, which is why no lesson in this domain can recommend a design without naming what an operator sees when it fails, what survives the partition, what repairs it afterwards, and the simpler thing to consider first.

How it works
  • The active region serves all writes and replicates its log to the passive region, synchronously or asynchronously.
  • The passive region applies the log and tracks its position; it may serve stale reads.
  • A detector observes the active region’s health across the region boundary and waits out a flap window before declaring anything.
  • On a declared loss, the old primary’s ability to commit is revoked — lease expiry, storage fencing token, credential or network revocation.
  • The standby drains whatever replication it can still receive, then is promoted to primary and begins accepting writes.
  • The routing layer is repointed: DNS records with a short TTL, a global load balancer, or an anycast withdrawal.
  • Caches, connection pools and runtimes warm under full load — the phase where an untested standby most often fails.
What can fail at the boundary
  • The detector fires on a network blip and triggers a failover no one needed.
  • The detector does not fire because the region is degraded rather than dead, and the outage runs long while everything reports partially healthy.
  • Fencing fails or is skipped, and the old primary continues committing.
  • DNS TTLs turn out to be an hour, so clients keep resolving to the dead region long after promotion.
  • The standby has less capacity than production and collapses under the full load it has never carried.
  • A schema or config drift makes the promoted region reject writes that the primary accepted.
How it fails — what an operator sees
  • Failover that hangs at 80%: traffic is repointed but a fraction of clients still resolve the old address for the length of the DNS TTL. The operator sees errors from a region that is supposed to be out of service, and cannot make them stop.
  • Silent data loss: after promotion, a set of orders acknowledged in the final seconds simply does not exist. Nothing errored; the only evidence is customer contact days later, and reconciling requires the dead region to be recoverable.
  • Standby collapse: the promoted region takes full load with cold caches and empty connection pools, and falls over within ninety seconds. The operator sees a second outage on top of the first, and the instinct to fail back is exactly wrong.
  • Split-brain after promotion: both regions accept writes for eleven minutes. The observable is a customer whose data alternates depending on which region their request lands in, and a uniqueness violation that appears when the two datasets are merged.
  • Drift discovered at the worst time: the promoted region cannot reach a third-party API because only the primary’s egress IPs were allowlisted. The failure is a 403 from an external service and there is no fix available inside the maintenance window.
Where coordination is required
  • None on the normal write path beyond whatever replication mode was chosen — this is why active-passive is cheap in steady state.
  • Failover requires the single hardest agreement in the domain: who is primary now, reached at the moment the network that would carry the agreement is broken.
  • Fencing is the substitute for agreement. Rather than proving the old primary has stopped, you make it *unable* to continue, which requires only the storage layer’s cooperation and not the old primary’s.
  • Synchronous replication moves coordination onto every write, converting an RPO into a latency and a joint-availability cost.
What still holds under failure
  • No divergence occurs while exactly one region can commit — the guarantee holds through the outage if fencing works.
  • Writes are unavailable from the moment the active region fails until promotion completes. That interval is the outage, and it is the RTO.
  • Reads may continue from the standby throughout, stale by the replication lag, if the read path was built to allow it.
  • Acknowledged writes inside the lag window are lost and no mechanism recovers them automatically.
How it recovers
  • Detect: alert on replication lag and on standby readiness independently of primary health, because the primary is healthy right up until it is not.
  • Contain: fence before promoting, always. If fencing is unavailable, do not automate promotion.
  • Recover: promote, repoint, and warm — expecting the warm phase to be the fragile one, and shedding load deliberately during it rather than letting it collapse.
  • Reconcile: capture the old primary’s unreplicated tail before rebuilding it, and treat the divergence as a business reconciliation rather than a database merge.
  • Verify: check the invariant and a sample of recent user-visible records, not just that the service returns 200. And record the real RTO, so the next planning conversation uses a measured number.
How you would know
  • Replication lag in seconds *and* in bytes, per stream, with an alert threshold expressed as an RPO the business has agreed to.
  • Standby readiness as a synthetic check: can it actually accept a write, right now, in a scratch table? Most standby monitoring proves only that a process is running.
  • Time since last failover exercise, on the dashboard, as a first-class reliability metric. It is the best single predictor of whether the next one works.
  • DNS TTL and observed client convergence time after a repoint — measured during an exercise, not assumed from configuration.
  • Configuration and schema drift between regions, computed continuously, because every item on that diff is a failover that fails.
When it helps
  • When the invariant is strict and conflicts are unacceptable — ledgers, inventory, anything where two writers is a correctness disaster.
  • When users are concentrated near one region, so the latency benefit of a second write path would be small anyway.
  • When the requirement is genuinely disaster recovery — a rare, severe event with an RTO in tens of minutes — rather than continuous availability.
  • When the team is small: one writer is dramatically less to reason about, and a simple design that is understood beats a sophisticated one that is not.
When it hurts
  • When the RTO requirement is minutes and the standby is cold or warm — the arithmetic simply does not reach it, no matter how good the runbook is.
  • When nobody will ever rehearse the failover, in which case you are paying for a standby and receiving a document.
  • When users on the far side need low write latency: passive regions do not serve writes, so their users pay the full RTT forever.
  • When the data is genuinely mergeable and availability matters more than a strict order — that is what [[region-active-active]] is for, and forcing it into active-passive buys an outage you did not need.
Simpler alternatives
  • Multi-zone within one region: survives the failure mode that actually happens most often, with a failover measured in seconds and no cross-region complexity at all.
  • Hot standby that serves read traffic — the same architecture, but continuously proven, for essentially the same money.
  • Partitioned ownership, so that losing a region costs you only its own slice rather than the whole write path — [[multi-region-write-models]].
  • Backup and restore with an explicitly accepted RPO of hours, which is cheaper, simpler, and far more likely to actually work than an unexercised warm standby.
  • Synchronous replication to the standby for the small subset of data where RPO must be zero, and asynchronous for everything else — the mixed answer most mature systems land on.

Runbook RTO five minutes, measured RTO twenty

Active-passive: simple to reason about, rarely tested
One writer means no conflicts, ever — that is a real advantage. The weaknesses are not conceptual: the failover is a sequence of steps whose times add up, and it is almost never exercised.
measured RTO
16.0 min
runbook RTO (what gets written down)
5.3 min
RPO
3.2 s
writes lost
≈ 899 orders
Detect
Page
Page · 1.5 min
Decide
Decide · 3.0 min
Fence
Fence · 1.5 min
Promote
Promote · 1.5 min
Repoint
Repoint · 3.0 min
Warm
Warm · 5.0 min
↑ steady state, degraded capacity
t+0 to t+16.0 min
Detect — health checks fail; the detector waits out the flap window30 s · 3.1%
Page — human is paged, opens laptop, reads runbook90 s · 9.4%
Decide — is it down, or is it the link? — this is the hard one180 s · 19%
Fence — revoke the old primary’s write lease — MUST precede promotion90 s · 9.4%
Promote — drain replication, then promote the standby90 s · 9.4%
Repoint — DNS TTL / global load balancer — a floor you set months ago180 s · 19%
Warm — cold caches, empty pools, JIT warm-up — often where the standby falls over300 s · 31%
Detection is usually the largest term, and it cannot be tuned aggressively without causing spurious failovers: a network degradation that fails health checks intermittently for eight minutes will trip an aggressive automatic failover that has no fencing. The warm-up phase — thundering herd onto an empty cache — is frequently the point at which the standby falls over, ninety seconds in. A failover performed six weeks ago is a routine operation; one performed never is a research project conducted during an outage.
State the RPO as a measured quantity. “Replication lag p99 is 3.2 s, therefore our worst-case data loss is roughly 3.2 s of writes, which at peak is about 899 orders.” And note when that lag is widest: under load — which is when failures happen. Your data-loss window is widest at the moment you are most likely to need it. Synchronous cross-region replication removes it at 90–200 ms per write, which is affordable for a ledger and not for a session store, so most systems end up doing both for different data.
Making the standby real. Route real traffic through it — even 5% of reads keeps config, capacity, certificates and dependency allowlists continuously honest. Fail over on purpose, quarterly, in business hours, announced, with the ability to abort. And remember that failback is a separate, harder, unrehearsed operation: when the failed region returns two hours later it has a divergent tail of writes that somebody usually discards quietly.
Typical RTOCostWhat is continuously proven
Cold (backups only)typicalHours to days~5% of productionThat backups exist. Not that they restore.
Warm (data replicating, app scaled down)typical15–90 minutes~30%That replication works. Not that the app boots there.
Hot (full scale, no traffic)typical5–20 minutes~100%That processes start. Not that they serve real requests.
Hot, serving readstypical2–10 minutes~100%Capacity, config, certificates, dependencies, and the read path — everything except the write promotion.
The standby spectrum. The last column is the one that predicts whether a failover works.
typicalThese bands are representative of teams that have written the runbook and rehearse rarely. A team that fails over quarterly beats them substantially; a team that never has will miss them by an order of magnitude. The timeline also shows one database — a real failover moves the store, cache, search index, object storage and broker, and their combined RPO is set by the worst one.

What people believe, and what is true

Claim

We have a standby region, so we are covered for a regional outage.

Reality

You have a copy of the data. Coverage requires a failover that works, which is an operational property that decays continuously and can only be maintained by exercising it.

Claim

Automatic failover is safer than manual because it is faster.

Reality

Automatic failover without fencing is strictly more dangerous, because it can create two primaries faster than a human would. Speed only helps after safety.

Claim

Our RPO is zero because we replicate continuously.

Reality

Continuous is not synchronous. Asynchronous replication has an RPO equal to the lag, which is non-zero by definition and largest under load.

Claim

Failback is just failover in reverse.

Reality

The recovered region holds divergent state the current primary does not. Failing back means either discarding it or reconciling it — a decision, not a procedure.

Claim

The standby is identical to production because it is the same Terraform.

Reality

Infrastructure is the smallest part of drift. Quotas, certificates, secrets, third-party allowlists, schema versions and warmed caches are where failovers actually die.

Go deeper

Only the levels this lesson can honestly fill — a missing level is a claim nobody had.

Overview

One region writes, one waits. No conflicts, ever. The cost is a failover that takes minutes, loses the replication-lag window of writes, and has probably never been tested.

Practical

Write the failover timeline with real measured numbers, including DNS TTL and cache warm-up, and compare it to the RTO you have promised. State the RPO as a measured lag in seconds and translate it into business units. Then fix the two things that matter more than the architecture: route some real traffic through the standby, and fail over on purpose every quarter.

Advanced

The correctness of active-passive rests entirely on there being at most one committer at any time, and that property is maintained by fencing, not by detection. This is worth stating precisely because it reorders the priorities: an accurate failure detector is a *latency* optimisation for the failover, while fencing is the *safety* mechanism, and a design that invests in the first while skipping the second is optimising the wrong term. Practically this means the promotion path should be built around a storage-level epoch or lease that the old primary’s writes fail against — the same construction as [[terms-and-epochs]] in consensus — so that a partitioned old primary’s writes are rejected by the storage layer whether or not it ever learns it was demoted.

Apply it

Build it, then break it
  • 🔧 Measure your own failover timeline end to end in a rehearsal, including DNS convergence and cache warm-up, and compare it with the documented RTO.
  • 🔧 List every way your standby could have drifted from production, then build a check for each one that runs daily.
Reason about this
  • A network degradation — not an outage — causes health checks to fail intermittently for eight minutes. Automatic failover is enabled with no fencing. Describe what the data looks like afterwards.
Interview questions
  • 💬 Your DR document says RTO 5 minutes. Walk me through the actual sequence and tell me the real number.
  • 💬 Why must fencing happen before promotion rather than after?
  • 💬 Your replication lag p99 is 4 seconds. State your RPO in business terms.
  • 💬 The failed region comes back online two hours later, with writes the new primary never saw. What do you do?