The idea: when does the primary say OK?
A primary database keeps its changes in a write-ahead log (PostgreSQL WAL, MySQL binlog) and streams that log to its replicas. All three replication modes ship the same log the same way. They differ in one thing only: when the primary tells the client "committed".
- Asynchronous: OK as soon as the write is in the primary's own WAL. The replicas get it a little later.
- Semi-synchronous: OK once one replica says it has the record (MySQL semi-sync, PostgreSQL
ANY 1 (…)). - Synchronous: OK once every listed replica says so (here PostgreSQL
FIRST 2 (R1, R2)).
The page runs three identical clusters on one clock: a client, a primary P, a near replica R1 (2 ticks away) and a far replica R2 (4 ticks away). The client sits 1 tick from P and commits w1, waits for OK, commits w2, and so on. The timelines on the right share one tick axis, so the three modes can be compared at a glance.
Commit latency: what you wait for
A commit costs the round trips the primary waits for. Async waits for none: 1 tick to P and 1 back, 2 ticks. Semi-sync adds a round trip to the nearest replica: 1 + 2 + 2 + 1 = 6 ticks. Sync adds a round trip to the slowest replica: 1 + 4 + 4 + 1 = 10 ticks. A client that commits one write at a time gets 10, 3 and 2 commits in the same 21 ticks (Demo 1: commit latency). Many clients in parallel win back throughput, but no amount of parallelism shortens a single commit.
RPO: what a crash loses
The recovery point objective (RPO) is how much committed data you may lose when the primary dies. When it does, a monitor (Patroni, Orchestrator, a cloud control plane) promotes the replica with the most log. Any write the client was told was committed but the new primary never received is gone.
Demo 2: primary crashes stops P at tick 10. Async had just answered OK for w5, but w5 was still on its way to R1: R1 is promoted and w5 is lost ✗. Semi-sync and sync lose nothing: every OK they sent was preceded by a replica's ack. Notice semi-sync's w2: the client never got an answer, yet R1 had it, so it survives. "No answer" means "unknown", not "lost". The old primary's extra WAL entries are thrown away when it is rewound (pg_rewind) or rebuilt.
When a replica is slow or down
Waiting is also a dependency. Demo 3: R1 is down: async does not notice; semi-sync still needs only one ack, but now it must come from the far R2, so each commit takes 10 ticks; sync with FIRST 2 (R1, R2) waits for R1 forever and no write commits at all. That is why real synchronous setups name more standbys than they wait for (FIRST 1 (r1, r2), ANY 1 (r1, r2)) or use a quorum of three.
Semi-sync falls back to async
MySQL semi-sync does not wait forever. After rpl_semi_sync_source_timeout (10 seconds by default, 10 ticks here) with no ack, the primary falls back to asynchronous replication and answers OK anyway; it switches back when a replica catches up. Demo 4: replicas cut off, then a crash cuts the network to both replicas. Semi-sync waits 10 ticks, falls back, then acks w1 to w3 with no copy anywhere. When P dies, async loses 8 acked writes, semi-sync 3, and sync none, because sync committed nothing. Setting rpl_semi_sync_source_timeout very high turns semi-sync into "block instead of lose".
The settings in PostgreSQL and MySQL
| Mode | PostgreSQL | MySQL |
|---|---|---|
| async | synchronous_standby_names = '' or synchronous_commit = local | default replication |
| semi-sync | synchronous_standby_names = 'ANY 1 (r1, r2)' | rpl_semi_sync_source_enabled = ON, rpl_semi_sync_source_wait_for_replica_count = 1, rpl_semi_sync_source_wait_point = AFTER_SYNC |
| sync (all) | synchronous_standby_names = 'FIRST 2 (r1, r2)' | not built in; Group Replication uses a Paxos majority instead |
| what "has it" means | synchronous_commit = remote_write (in OS memory), on (flushed), remote_apply (replayed) | the replica wrote it to its relay log |
Choosing a mode
| Async | Semi-sync (1 of N) | Sync (all) | |
|---|---|---|---|
| commit latency | local disk only | + round trip to the nearest replica | + round trip to the slowest replica |
| acked writes lost on failover (RPO) | the replication lag ✗ | none, unless it fell back to async | none ✓ |
| a replica goes down | no effect | slower, or falls back after the timeout | writes block |
| typical use | read replicas, cross-region copies | HA pair in one region | small clusters with spare standbys, financial ledgers |
See also Database Replication for replication lag, stale reads and split brain, MySQL Replication Internals for the binlog, GTIDs and the semi-sync plug-in, Quorum Reads and Writes for W-of-N acks without a leader, and Leader-based vs Leaderless Replication.
What the page leaves out
Many concurrent clients and group commit, the difference between received, flushed and applied on the replica (stale reads), failure detection time and fencing of the old primary, semi-sync switching back on after a replica catches up, the AFTER_COMMIT wait point (where other sessions can see a write that may still be lost), quorum commit across three or more replicas, cascading replicas, and real time: here every network hop is a whole number of ticks and a crash also drops every message on the wire.