When a system is described as synchronously replicated, the acknowledgement may mean the standby received the change, flushed it to its own log, or replayed it so queries can see it. Why does that distinction matter, and what does each level buy you?
answer
- chain: local buffer, local flush, remote received, remote flushed, remote applied
- remote_write survives primary loss only
- on = flushed = zero data loss
- remote_apply buys read visibility, not durability
- AFTER_SYNC closes the AFTER_COMMIT visibility window
basics
~20 sEach level survives a different failure. Received in memory survives losing only the primary; flushed to the standby's disk also survives the standby restarting; replayed additionally makes the change visible to readers on the standby. Later levels add latency, so pick the earliest level that covers the failure you care about.
solid answer
~50 sThink of a spectrum of when the primary answers the client. - **Local only**: durable on the primary's disk, nothing waited for remotely. Survives a primary crash-restart, not primary loss. - **Standby received (in memory)**: survives losing the primary, but a simultaneous restart of the standby can lose the change since it was never flushed. - **Standby flushed to its log**: survives losing the primary and a standby restart. This is the usual meaning of zero data loss, and MySQL's semi-synchronous AFTER_SYNC sits here. - **Standby replayed/applied**: additionally guarantees that a read on that standby sees the change, which fixes read-after-write for readers of that standby. It is the slowest, because replay can queue behind conflicting activity. PostgreSQL names these on `synchronous_commit`: `off`, `local`, `remote_write`, `on`, `remote_apply`. Note `off` is a separate axis: it relaxes the primary's own flush, trading local durability for throughput without any replica involved.
code
sql · 5 lines-- strict: wait for a standby to flush before acknowledging
SET LOCAL synchronous_commit = 'on';
-- relaxed, for bulk telemetry in the same database
SET LOCAL synchronous_commit = 'local';go deeper
Know that acknowledged can mean received, flushed, or applied, and that later means safer and slower.
Name the levels with their settings and attach the failure each survives; explain that apply level is about read visibility rather than durability.
Pick a level from a stated failure requirement, set it per transaction class, and reason about correlated failures and tail latency from standby replay.
Treat the level as a policy per data class with an explicit cost of loss, and decide whether read-visibility needs are better met per read path than by paying apply latency on every commit.
## Why one word hides four guarantees Committing a transaction is a chain of steps: write the change records to the primary's log buffer, flush that buffer to durable storage, ship the records to a standby, the standby writes them into its memory, the standby flushes them to its own log, the standby replays them into its data files so its queries can see them. Calling a system synchronous only says the primary waits somewhere in that chain. Which link you choose decides which failure you survive and how much latency each commit pays. ## The levels **Nothing waited for, not even locally.** The primary acknowledges after writing to its log buffer and flushes a moment later (PostgreSQL `synchronous_commit = off`). Throughput rises sharply because commits stop paying a flush each, but a crash of the primary itself can lose the last fraction of a second of committed transactions. Importantly, this is not corruption: the database recovers to a consistent earlier point. It is a local durability decision that exists independently of replication, and it is the meaning of asynchronous commit as distinct from asynchronous replication. **Local durable only** (`local`). The primary flushes its own log and answers. This is standard single-node durability and, combined with an asynchronous standby, is the common default. **Standby received** (`remote_write` in PostgreSQL). The primary waits until the standby has written the records into its operating-system buffers. If the primary is lost, the data exists on another machine. But if the standby process or host restarts before flushing, those buffers evaporate. It protects against the single most likely event, primary loss, at close to the lowest possible added cost. **Standby flushed** (`on` in PostgreSQL; MySQL semi-synchronous with AFTER_SYNC). The standby forces the records to its own durable storage before acknowledging. Now the transaction survives losing the primary and a simultaneous standby restart. This is the level people normally mean by zero data loss, and it is the right default for systems of record. **Standby replayed** (`remote_apply`). The standby also replays the change into its data pages so its own queries return it. This buys a read guarantee, not extra durability: a client that commits and then reads that standby sees its own write. The cost is real, because replay on a standby can be delayed behind conflicting activity, so tail commit latency becomes hostage to standby query load. ## MySQL's phrasing MySQL calls its middle ground semi-synchronous: the source waits for at least one replica to acknowledge receipt of the events before completing the commit to the client. The two historic variants matter. AFTER_COMMIT commits locally first and then waits, which leaves a window where other sessions on the source can see a transaction that no replica has yet, so a failover can lose a transaction that some client already observed. AFTER_SYNC waits before making the transaction visible, closing that window; it is the default in modern versions and the one to name. ## Choosing a level Start from the failure you must survive. Losing the primary host is the common one: `remote_write` or `on` covers it, and `on` is the safe answer. If you also worry about correlated failures (a rack power event that restarts both machines), you need the flush level, not merely received. If your motivation is that readers of the standby must see the write, that is `remote_apply`, but consider whether a per-read fix (routing that read to the primary, or waiting on a replication position) is cheaper than paying replay latency on every commit. A useful discipline: the level should be chosen per transaction where the engine allows it. It is legitimate to run the ledger writes at the strict level and the telemetry inserts at a relaxed one in the same database, because the cost of losing each differs by orders of magnitude. ## What none of them give you No level protects transactions that were never acknowledged; those are simply lost and the client must retry, which is why idempotent write APIs matter. No level makes the standby's data visible to readers except the apply level. And no level removes the availability question: waiting on a standby means a missing standby can block commits, which is a separate configuration problem. ## How to present it Walk the chain in order, attach a survived failure to each link, name the concrete settings (`synchronous_commit` levels; semi-synchronous AFTER_SYNC versus AFTER_COMMIT), and finish by choosing a level from a stated failure requirement rather than reciting a default.
- What does synchronous_commit = off do, and is it about replication?No, it is about local durability: the primary acknowledges the commit before its own log flush completes, so a crash of the primary can lose the last fraction of a second of committed transactions. The database still recovers to a consistent state, and no replica is involved. It is a throughput-for-durability trade that can be set per transaction.
- Why did MySQL move from AFTER_COMMIT to AFTER_SYNC as the semi-synchronous default?With AFTER_COMMIT the source commits locally and only then waits for the replica, so other sessions could read a transaction that no replica had received. If the source then failed, a transaction that clients had already observed would vanish after failover. AFTER_SYNC waits before making the transaction visible, so nothing anyone could have read is lost.
saying these in an interview costs you the question
- Treating synchronous as a single setting with one meaning
- Believing the standby-received level survives a standby restart
- Expecting the flush level to make standby reads current
- Confusing synchronous_commit = off with asynchronous replication; it is local durability and involves no replica
- Assuming the level must be global, when engines allow it per transaction