← Back to Logs

How Database Replication Actually Works

Try the interactive lab for this articleTake the quiz (6 questions · ~5 min)

Database replication is usually explained with one cheerful sentence: copy the data from one server to another so you have redundancy and scale. That sentence hides almost all of the engineering.

A database is not a pile of rows sitting still on disk. It is a stream of commits arriving in a particular order, turning pages dirty in memory, writing log records, flushing some bytes now and some later, exposing snapshots to concurrent readers, and occasionally promoting a former replica when the original primary dies. If you only copy the visible table contents and ignore the ordering, durability points, and recovery state underneath them, you do not have replication. You have a future incident.

What matters is not that two machines eventually contain similar rows. What matters is that they agree on which transaction committed, in what order, at what log position, and whether a failover can happen without losing acknowledged work. That is why serious replication systems talk in terms like LSN, GTID, binlog position, commit index, relay log, replay lag, synchronous quorum, and timeline.

This article explains database replication from the inside. We will look at how a new replica is born from a consistent snapshot, how change streams move from primary to replica, why physical and logical replication solve different problems, what synchronous replication really waits for, why replica lag is not one number, and how failover either preserves truth or creates split brain depending on the control plane wrapped around it.

Replication Is About Ordered History, Not About Copying Files

Imagine a PostgreSQL primary in Amsterdam processing two transfers on the same account:

  1. subtract €70 for an online purchase
  2. add €2,000 for payroll

The final balance depends on both transactions, but the database also cares about the exact order in which they committed, the row versions each transaction saw, the index updates they produced, and the write-ahead log records that made them durable. If you pause the server and copy the data directory badly, or if you copy the table at one moment and the indexes at another, you may capture something that never existed as a valid database state.

Replication therefore starts with a stricter idea of truth: the database's history is an ordered log of committed changes plus enough metadata to recover that history correctly. PostgreSQL uses WAL positions called LSNs. MySQL uses binary log coordinates and, in modern setups, GTIDs. SQL Server has log sequence numbers. Oracle has SCNs. The names differ, but the principle is the same. A replica needs a stable answer to the question "up to exactly which change have I received and applied?"

That is why replication status screens are full of positions instead of row counts. A row count can match while the histories differ. Two databases can both contain ten million rows, but if one has applied an UPDATE and the other has not, or if both applied two conflicting updates in different orders, they are not equivalent systems.

This also explains why the old mental model of "master copies files to slave" is wrong for most modern engines. The interesting unit is not the whole file. It is the ordered stream of changes within the storage engine or the transactional layer. Replication is basically saying: take the primary's durable history, ship it elsewhere, and replay it in the same causal order.

You can sketch the idea like this:

client commits tx 812
primary appends log record at LSN 0/7A9C1200
primary flushes log locally
replica receives stream through 0/7A9C1200
replica flushes received bytes
replica replays change into its own pages
replica reports replay LSN 0/7A9C1200

Every operational question follows from that chain. Did the client wait only for local flush, or also for remote flush? Did the replica merely receive bytes, or did it replay them? Can reads on the replica see the change yet? If the primary dies now, does the replica have all acknowledged commits? The hard part of replication is giving precise answers to those questions under failure.

A New Replica Starts With A Consistent Snapshot, Then Catches Up Through The Log

A fresh replica cannot begin by listening to the live stream alone. The primary may already contain terabytes of data. The replica first needs a baseline copy that represents one consistent point in time, and only then can it apply subsequent changes from the log.

That baseline copy is often called a base backup, clone, or snapshot. The details vary by engine, but the shape is familiar.

  1. choose a log position that will anchor the copy
  2. create a consistent snapshot of the data files or table contents
  3. record the anchor position
  4. start streaming all changes after that position
  5. replay until the replica catches up to the primary

PostgreSQL's pg_basebackup does exactly this. It instructs the primary to start a backup, takes a consistent file-level copy, records the starting WAL position, and then the replica replays WAL from there. MySQL tools such as XtraBackup or the built-in clone plugin solve the same problem with MySQL's storage and binary log model. Distributed systems built on Raft often transmit a snapshot plus the log entries after the snapshot index.

The word "consistent" matters. If you simply rsync a live database directory without cooperation from the engine, file A may come from 14:00:01 and file B from 14:00:19, while hundreds of transactions commit in between. Recovery will not know how to interpret that mess unless the engine deliberately created a restorable checkpoint boundary around it.

That is why snapshot creation is tied to logging. The snapshot gives you the bulk bytes efficiently. The log gives you everything that changed before, during, and after the copy. Together they define a valid starting point.

In practice the flow often looks like this:

T0  primary checkpoint or backup start marker written
T1  snapshot copy begins
T2  clients keep writing to primary
T3  log records after T0 accumulate in WAL or binlog
T4  snapshot copy finishes
T5  replica starts replaying from T0 forward
T6  replica reaches current head and enters steady streaming mode

This is also why long-running backup windows increase pressure on log retention. If the replica or backup consumer started at T0, the primary must keep every WAL segment or binlog file after T0 until the consumer no longer needs them. A slow copy over a WAN link from Madrid to Helsinki can therefore fill the primary's log disk if retention is not planned properly.

A common misconception is that the snapshot has to be perfectly up to date when it finishes. It does not. It only has to be internally consistent and tied to a known log position. The replay step is what turns an older snapshot into a current replica.

Physical Replication Ships Storage Engine Changes

Physical replication moves low-level storage changes from one machine to another. In PostgreSQL this means WAL records that describe page and tuple changes inside the engine. In SQL Server log shipping and Always On availability groups work with the transaction log. Oracle Data Guard ships redo. The replica is effectively another copy of the same engine state being driven by the primary's storage history.

The biggest advantage is fidelity. Because the stream is close to the storage engine, the replica reproduces exactly what the primary did. Index updates, visibility metadata, heap changes, page splits, and internal housekeeping all travel in the same stream. The replica does not need to re-interpret a high-level statement and hope it behaves the same way. It replays the engine's own record of what happened.

That gives physical replication several nice properties:

  • it is usually fast and compact
  • it preserves exact transactional ordering
  • it keeps indexes and internal structures in sync automatically
  • it is well suited to failover because the replica is materially the same database engine state

It also imposes constraints.

A physical PostgreSQL replica is still PostgreSQL. Usually it wants the same major version family and compatible storage format. You cannot normally take PostgreSQL WAL and replay it into MySQL, or into an analytics warehouse with different page layout, or into a search engine. The stream is too engine-specific.

A simplified PostgreSQL configuration for streaming physical replication might look like this:

wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
hot_standby = on
synchronous_commit = on

and if you want a commit to wait for one named synchronous standby:

synchronous_standby_names = 'FIRST 1 (fra_replica)'

Under the hood the primary's walsender process reads WAL records, packages them into the replication protocol, and streams them to the standby's walreceiver. The standby writes those bytes into its own WAL files, flushes them according to its durability rules, and a replay process applies them to data pages.

That same low-level fidelity is what makes physical replication excellent for hot standby, disaster recovery, and near-zero-data-loss failover. It is also why it is a poor fit if you want selective replication, schema transformation, or fan-out into systems that do not share the same engine internals.

Logical Replication Ships Higher-Level Changes, And That Changes The Trade-Offs

Logical replication moves higher-level change events instead of raw storage engine state. The event might be a row insert, row update, row delete, or in some systems a SQL statement or change-data-capture record. This is useful when the destination should not be a byte-exact copy of the source.

Suppose a retail platform in Rotterdam writes orders into PostgreSQL but wants to feed a fraud service, a search index, and a data lake. Physical WAL is too engine-specific. What those downstream systems need is something like:

{
  "table": "orders",
  "op": "UPDATE",
  "primary_key": { "id": 481922 },
  "before": { "status": "authorised" },
  "after": { "status": "captured" },
  "commit_lsn": "0/7A9C1200"
}

That is a logical event. PostgreSQL can produce it through logical decoding. MySQL can produce a similar stream from the binary log using row-based replication and external consumers such as Debezium. Many cloud databases expose equivalent CDC feeds.

Logical replication solves problems physical replication cannot:

  • replicate only selected tables
  • transform schemas on the way out
  • feed caches, search, streams, and warehouses
  • replicate across versions or heterogeneous systems
  • support zero-downtime migrations with dual write validation

But it loses some guarantees that physical replication gets for free.

First, logical replication depends on keys and semantics above the storage layer. If a row update arrives at the target, the target needs enough identity to find the right row. On PostgreSQL that often means a primary key or replica identity. Without it, deletes and updates can become awkward or expensive.

Second, not every change is automatically represented. PostgreSQL logical replication historically replicates table data changes, not every DDL statement. Sequence behaviour can also surprise people. Schema migrations often need separate tooling and ordering discipline.

Third, statement-based logical replication can be dangerous when the statement is non-deterministic. Consider:

UPDATE invoices
SET due_at = now() + interval '14 days'
WHERE batch_id = 381;

If one replica re-executes that statement a few seconds later than another, now() is different. Row-based logging solves that by sending the resulting row values, not the original expression. This is one reason MySQL has moved many serious systems toward row-based binary logging rather than pure statement mode.

So the trade-off is clear. Physical replication is best when you want another copy of the same engine state. Logical replication is best when you want portable change events or selective data movement. Many real systems use both at once: physical replicas for failover and read scaling, logical streams for integration.

The Primary Commit Path Decides What Acknowledged Work Really Means

When an application receives COMMIT OK, what exactly has happened? The answer depends on the replication mode.

With asynchronous replication, the primary usually acknowledges once the commit is durable locally. The replica may not have received anything yet. This is excellent for latency because the client does not wait for another machine or another datacentre. It is also a direct statement of risk: if the primary dies before the replica has flushed the relevant log records, recently acknowledged commits can be lost during failover.

With synchronous replication, the primary delays the acknowledgement until one or more replicas confirm a stronger condition, usually remote write, remote flush, or remote apply depending on the engine and configuration. This reduces or removes data loss on failover, but it adds network and replica storage latency to the critical path of every commit.

A simplified asynchronous timeline looks like this:

client -> primary: COMMIT
primary: append log
primary: fsync local log
primary -> client: COMMIT OK
primary -> replica: stream log
replica: receive later
replica: flush later
replica: replay later

A synchronous timeline looks more like this:

client -> primary: COMMIT
primary: append log
primary: fsync local log
primary -> replica: stream log
replica: fsync received log
replica -> primary: durable up to LSN X
primary -> client: COMMIT OK

That one extra round trip can turn a sub-millisecond local commit into a multi-millisecond cross-site commit. If your synchronous standby is in another city, say Frankfurt waiting on Stockholm, the network becomes part of your transaction latency budget.

MySQL's semi-synchronous replication sits in the middle. In broad terms, the primary waits until at least one replica acknowledges receipt of the transaction into its relay log, but not necessarily application into table pages. The exact failure window depends on settings and versions. That nuance matters, because "received somewhere else" is weaker than "durably flushed and replayable somewhere else".

This section is where a lot of architecture hand-waving collapses. A slide deck might say "two replicas, highly available". The real question is: what does the client wait for, and what failure right after the acknowledgement can still lose data? If the answer is "primary local disk only", the system is fast but not zero-loss. If the answer is "quorum durable across sites", the system is safer but slower.

There is no free option. Replication mode is a contract between latency and recovery semantics.

A Replica Does Three Different Jobs: Receive, Flush, And Apply

People often talk about a replica as if it were either caught up or not caught up. Real replicas have at least three separate positions.

  1. received: bytes have arrived over the network
  2. flushed: those bytes are durable on the replica's own disk
  3. replayed or applied: the replica has actually incorporated the changes into visible database state

These are not the same thing.

A standby under load may have the latest WAL received but still be behind on replay because its apply process is busy, blocked, or intentionally paused. Another standby may flush quickly but expose older query results because long-running read traffic delays visibility of replayed changes.

PostgreSQL exposes this distinction clearly. On the standby you can inspect positions such as:

SELECT
  pg_last_wal_receive_lsn(),
  pg_last_wal_replay_lsn(),
  pg_last_xact_replay_timestamp();

On the primary you can compare the standby's reported write, flush, and replay LSNs through pg_stat_replication. MySQL exposes similar ideas through SHOW REPLICA STATUS\G, with fields that reveal the IO thread, SQL thread, relay log progress, and whether lag is in transport or apply.

Why does this matter? Because applications and operators ask different questions.

  • Can this standby take over without losing committed data?
  • If I run a read here now, will I see my last write?
  • Is the network slow, or is replay blocked by local IO?
  • Is the replica durable enough to count for synchronous commit?

Those are different questions because they care about different stages.

Imagine a standby in Milan that has received WAL through LSN 0/7A9C1200 but only replayed through 0/7A9B9000. A failover might still be safe if the WAL is flushed durably and the standby can replay on promotion. But a read query routed there right now may still miss the latest order status change.

This receive/flush/apply split is also why serious monitoring separates transport lag from replay lag. A single number like "replica delay: 3 seconds" compresses too much. If the network is healthy but replay is blocked on local disk saturation, the fix is different from a transatlantic packet loss problem.

Replica Lag Is Not One Number, And Read Scaling Is Never Free

The most common reason teams add replicas is not failover. It is read scaling. That is reasonable, but it leads to a subtle trap: the application starts treating the replica as if it were a free copy of the primary, just with more capacity. In asynchronous systems, it is not.

Replica lag comes from several different sources.

Transport lag. The primary generated changes, but the replica has not received them yet. Causes include network congestion, packet loss, sender backpressure, or a disconnected replica.

Flush lag. The replica received the bytes but is slow to make them durable. Usually this is local disk pressure or contention with checkpoints and background writes.

Apply lag. The replica has durable log records waiting, but replay into table pages is behind. Reasons include CPU starvation, expensive single-thread apply paths, lock conflicts, or long-running queries.

Visibility lag. The engine may have replayed changes, but your read path still sees older state because of transaction snapshot semantics, proxy routing, or cache staleness above the database.

An application in Amsterdam that writes to the primary and then immediately reads from a Brussels replica may therefore see stale data for perfectly healthy reasons. That stale read is not necessarily a bug in the database. It is a direct consequence of asynchronous replication.

This shows up in familiar product bugs:

  • user changes password, then refreshes profile and sees old value
  • order status flips to shipped on primary, but customer page on replica still shows processing
  • access rights are revoked, but a replica-backed API still authorises the old privilege for a second or two

The fixes are architectural, not magical.

  • route read-after-write traffic to the primary
  • use session stickiness after important writes
  • wait for a replica position before serving a dependent read
  • expose causal tokens or LSN watermarks through the application layer
  • avoid replicas for security-critical immediate-consistency checks

Some systems explicitly pass replication positions around. The primary returns the commit LSN for a write. A later reader may wait until the chosen replica has replayed at least that LSN before serving the request. This preserves read-your-write semantics while still using replicas.

Long-running analytics queries create another class of lag. On PostgreSQL hot standbys, recovery conflicts can appear when replay wants to remove or change row versions that an old standby snapshot still needs. The operator then chooses between cancelling the query or letting replay fall further behind. That is a real trade-off in production reporting clusters.

Read scaling, in other words, buys capacity by selling freshness. The amount sold depends on topology and configuration.

Failover Is A Controlled Lie About Who The Primary Is

When a primary dies, the system has to pick a new writer. This sounds simpler than it is.

Promoting a replica is not just flipping a label in DNS. The control plane must answer four questions correctly.

  1. Which node has the most complete durable history?
  2. Is the old primary really dead, or merely partitioned?
  3. How do we prevent both nodes from accepting writes at once?
  4. How do clients discover the new writer quickly and safely?

The second and third questions are where split brain comes from. If the old primary in Frankfurt is isolated from the cluster manager but still alive enough to accept writes from some clients, and a replica in Dublin is promoted at the same time, you now have two divergent histories. Both sides believe they are primary. Reconciling that later is painful because databases do not merge arbitrary conflicting histories automatically.

That is why serious failover stacks include fencing. Fencing means cutting off the old primary's ability to serve writes before or during promotion of the new one. That may involve power control, cloud instance isolation, storage lease revocation, VIP movement under strict lock, or a consensus system such as etcd deciding who owns leadership.

A disciplined failover sequence looks more like this:

monitor detects primary failure suspicion
quorum confirms node is unavailable
control plane fences old primary
most up to date replica is selected
replica finishes pending replay and promotes
routing changes to new primary
former primary is rebuilt or rejoined later as replica

Notice the phrase "most up to date replica". If you choose the geographically closest replica instead of the most advanced durable one, you may discard acknowledged commits needlessly.

PostgreSQL makes this visible through timeline history. Once a standby is promoted, it begins a new timeline. Other standbys need to follow the new timeline if they are to remain part of the same future history. MySQL with GTID-based failover has its own version of the same problem: determine which transactions each candidate has, pick the best candidate, then re-point others carefully.

Failover speed and failover safety also trade against each other. If you wait longer to be sure the old primary is really dead, you reduce split-brain risk but increase outage time. If you fail over aggressively on weak evidence, you cut downtime at the price of possible double-primary disaster.

Replication Needs Retention, Backpressure, And Rebuild Paths

The happy-path replication diagram leaves out a dull but essential fact: the primary must retain history until every consumer that matters has safely moved past it.

On PostgreSQL, replication slots can pin WAL segments so a slow standby or logical consumer does not miss data. On MySQL, binary logs must be retained until replicas have consumed them. On any engine, that means a stuck or abandoned consumer can fill disks on the primary by preventing log cleanup.

This is not an edge case. It is one of the most common operational replication failures. Someone creates a logical replication slot for a migration, forgets it, and WAL retention grows for days. Or a replica in another region disconnects, the primary keeps binlogs for it, and the log volume spikes until the filesystem goes read-only.

Backpressure appears in other places too.

  • slow disks on the replica create replay backlog
  • single-thread apply paths cap throughput on row-heavy workloads
  • network links saturate during catch-up after maintenance
  • large transactions produce long apply stalls and bursty lag graphs
  • DDL can block apply or require special ordering across consumers

The rebuild path matters because not every lagging replica should be nursed back to health. Sometimes the fastest recovery is to destroy it and build a fresh one from a new snapshot. That decision depends on data volume, log retention window, available bandwidth, and recovery time objective.

For example, if a replica in Warsaw is three days behind and the required WAL segments have already been removed, there is nothing to replay. The only honest answer is re-seed from backup and catch up again. Teams that never automate this path end up treating every replica failure like a custom incident.

Schema change discipline also lives here. Physical replicas usually receive storage-level effects of DDL automatically because the primary's engine logged them. Logical consumers may not. If your application writes a new column before downstream consumers know the schema, the CDC pipeline can break. Good rollout plans sequence schema evolution, application deployment, and consumer upgrades deliberately.

Replication is therefore not just about streaming live changes. It is also about life cycle management of the history itself.

In Distributed Databases, The Replication Log Becomes The Database

Single-primary replication teaches one deep lesson: ordered durable logs are the real source of truth. Distributed consensus systems push that lesson further. In Raft- or Paxos-based databases, the replicated log is not an implementation detail under the database. It is the database's coordination core.

Take a Raft-backed range in CockroachDB, YugabyteDB, or a similar system. A leader receives a write, packages it as a log entry, and sends it to followers. Once a majority of replicas have durably appended the entry, the entry becomes committed. Only then is it applied to the state machine and acknowledged externally.

That sounds familiar because it is the replication story again, with quorum semantics made explicit.

  • local single-node durability becomes majority durability
  • failover becomes leader election over the same log history
  • replay becomes state machine apply
  • snapshot plus catch-up log is how slow followers rejoin

The interesting difference is that there is no separate afterthought called failover layered on top of asynchronous replicas. Leadership, commit, and replication are part of one protocol. If a follower is behind, the leader knows exactly which log index it needs. If a leader dies, a new one can only be elected if it has a sufficiently up to date log according to the protocol's safety rules.

This does not make distributed databases simple. It makes the safety boundaries clearer. A client write is not committed because one machine fsynced it. It is committed because a quorum recorded it in order.

That is also why these systems pay a latency cost. If your replicas are spread across Paris, Frankfurt, and Stockholm, a quorum write requires inter-node round trips before commit. You buy a stronger failover story by moving replication into the centre of every write.

Traditional primary-replica databases and consensus databases therefore sit on a spectrum, not in separate universes. Both revolve around replicated history. They differ mainly in when remote durability becomes part of the commit contract.

Replicas Are Not Backups, And Backups Are Not Replicas

Teams often talk about replicas and backups in one breath because both create more than one copy of the data. Operationally they solve different problems.

A replica is mainly for availability and read distribution. It is built to stay close to the primary and keep consuming new history. If the primary dies, the replica is the candidate that can take over quickly.

A backup is mainly for recovery to an older point in time. It is built to let you restore after corruption, operator mistakes, ransomware, or a bad migration that was faithfully replicated everywhere.

This distinction matters because replication eagerly copies some failures too.

If an application in Vienna runs:

DELETE FROM invoices;

without a WHERE clause, physical replicas will happily replay that delete. Logical downstreams may also consume the same destruction as a perfectly valid sequence of row changes. Replication preserved history correctly. It just preserved a bad history.

The same applies to many kinds of corruption. If the primary writes incorrect bytes because of an application bug, or if a migration drops a needed column, or if someone truncates the wrong table, replicas usually amplify the blast radius by spreading the mistake quickly and efficiently.

Backups are the defence against that class of incident. They let you rewind to a point before the mistake. On PostgreSQL that usually means a base backup plus archived WAL for point-in-time recovery. On MySQL it means a physical backup plus binlogs, or another equivalent combination. The important property is that the backup history is stored separately enough that normal replication and retention churn do not erase your only path back.

This also changes how you test them.

  • A replica is tested by failover drills, catch-up speed, and promotion correctness.
  • A backup is tested by full restore drills, recovery-time measurement, and point-in-time restore accuracy.

One cannot substitute fully for the other. Promoting a replica proves almost nothing about your ability to restore last Tuesday's state into an isolated environment in Copenhagen and recover only up to 09:14 before a destructive batch job started. Conversely, a perfect backup restore process does not prove your hot standby can take over in thirty seconds under live traffic.

Good systems therefore keep both stories healthy. They run replicas close enough to absorb node or site loss, and they keep backup and log archives far enough away, and retained long enough, to survive replicated mistakes. When someone says, "we have replicas, so we are covered," the missing question is: covered against what?

For availability, replicas are the answer. For historical recovery, they are only part of the answer.

What Operators Actually Watch, And What Usually Breaks First

If you operate replicated databases, you end up caring about a small set of numbers obsessively.

On the primary:

  • current log generation rate
  • replication send lag per replica
  • retained WAL or binlog volume
  • checkpoint pressure and disk saturation
  • synchronous standby health if commits depend on it

On each replica:

  • receive position versus primary head
  • flush position versus receive position
  • replay position versus flush position
  • replay delay in time and bytes
  • apply worker health, errors, and conflicts
  • storage headroom for relay logs or WAL

On the routing layer:

  • which node is primary right now
  • failover detection time
  • replica selection policy for reads
  • whether applications accidentally depend on stale reads

The failure patterns are repetitive.

A replica loses connectivity and the primary's retained log volume climbs silently. A reporting query blocks replay and suddenly your "low lag" standby is twenty minutes behind. A synchronous standby in another city starts having packet loss, and application latency jumps because commits are now waiting on retransmits. A failover script promotes the wrong node because it chose the lowest ping time instead of the highest durable log position. An abandoned logical slot fills the WAL disk. A rebuild takes too long because the team never practised snapshot seeding over the actual WAN path.

The important point is that replication failures usually begin as boring control-plane and capacity problems before they become correctness problems. By the time users notice stale reads or missing writes, the metrics have often been shouting for an hour.

One useful habit is to think in bytes as well as time. "Replica lag is 4 seconds" sounds manageable until you realise the primary is generating 180 MiB of WAL per second during peak trading, which means the standby is 720 MiB behind and may need serious IO just to catch up. Time lag without change volume is incomplete information.

Another useful habit is to test failover on purpose. Not on a whiteboard, on real systems. Measure how long clients take to reconnect, whether any writes are lost, whether replicas follow the new primary correctly, and how quickly the old primary is fenced. Replication that only exists in dashboard green lights is not a finished system.

Replication Is A Consistency Contract Wrapped Around A Transport

At the surface, replication looks like data movement. Underneath, it is a promise about history.

A replica is trustworthy only if you know where its history came from, how far it has progressed, what a commit acknowledgement meant at the primary, and what failover rule decides who is allowed to write next. The snapshot step, the log stream, the receive/flush/replay pipeline, the retention machinery, and the failover control plane all exist to preserve that promise.

That is why replication discussions that stay at the level of "one writer, two readers" are usually missing the hard parts. The hard parts are ordered commit history, log positions, lag semantics, fencing, and rebuild discipline.

Once you see replication that way, most operational puzzles become easier to reason about. A stale read is no longer mysterious. It is a read served before the relevant history was replayed. A dangerous failover is no longer abstract. It is a promotion decision made without certainty about which history is newest and exclusive. A synchronous write latency spike is no longer random. It is the cost of waiting for remote durability because that is the contract you chose.

Replication is not copying data twice. It is teaching another machine to tell exactly the same story, in exactly the same order, under conditions where one of them may die at any moment.