PostgreSQLXIV · Replication, Slots and Read ReplicasReplication
Synchronous replication and the trade-off it makes
What you'll learn
- Distinguish the synchronous_commit levels by the guarantee each buys
- Configure synchronous_standby_names, including quorum forms
- Recognise and prevent the availability failure synchronous replication introduces
- Decide whether synchronous replication is warranted
Prerequisites
Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27
Asynchronous replication means a commit is durable on one machine. If that machine’s storage is destroyed between the commit and the standby receiving it, the transaction is lost while the client believes it succeeded.
Synchronous replication closes that window, and the price is not subtle.
The levels
synchronous_commit has five values. Two concern only local durability
and were covered in lesson XII-01; three concern the standby.
| Value | Commit returns when | Survives |
|---|---|---|
off | WAL is in the buffer | Nothing. Crash loses recent commits |
local | WAL is flushed locally | Primary crash. Not primary loss |
remote_write | Standby’s OS has it | Standby process crash. Not standby host loss |
on | Standby has fsynced it | Loss of either machine |
remote_apply | Standby has replayed it | Same as on, plus read-your-writes |
on is the default, and on a cluster with no synchronous_standby_names
it means local flush only. Setting synchronous_commit = on does not
make replication synchronous. Two settings are required.
The measured cost
$ pgbench -U postgres -c 8 -j 4 -T 20 postgres setting tps latency avg relative
----------------- ------- ----------- --------
local 1624 4.926 ms 100%
remote_write 1625 4.923 ms 100%
on (remote flush) 1474 5.428 ms 91%
remote_apply 1462 5.470 ms 90%
(asynchronous baseline, synchronous_standby_names empty:
1618 tps / 4.940 ms — indistinguishable from 'local')Configuring it
# on the primary
synchronous_standby_names = 'standby1'
synchronous_commit = on
The name matches the standby’s application_name, which comes from
primary_conninfo. Set it explicitly:
primary_conninfo = '... application_name=standby1'
Without it, the default is walreceiver for every standby, and you
cannot distinguish them.
$ SELECT application_name, sync_state, sync_priority FROM pg_stat_replication; application_name | sync_state
------------------+------------
walreceiver | syncsync_state is the only trustworthy confirmation. Anything else is
belief.
Quorum forms
synchronous_standby_names = 'standby1' # exactly this one
synchronous_standby_names = 'FIRST 1 (s1, s2)' # s1 preferred, s2 fallback
synchronous_standby_names = 'ANY 1 (s1, s2, s3)' # any one of three
synchronous_standby_names = 'ANY 2 (s1, s2, s3)' # any two of three
ANY 1 (s1, s2) is the form that makes synchronous replication
survivable in practice, and the reason is the next section.
When it is warranted
Synchronous replication is right when losing an acknowledged transaction is worse than being unavailable: payments, ledgers, regulatory records, anything where “we told them it succeeded and it did not” is the worse outcome.
It is wrong when availability matters more than the last few transactions, which is most systems. A well-monitored asynchronous standby with lag measured in milliseconds — as measured in lesson XIV-04, 2.5 ms at 1,623 tps — has an RPO of milliseconds without the availability coupling.
There is a middle path that is often the right answer: synchronous_commit
is a per-transaction setting.
-- most work: asynchronous, fast
SET synchronous_commit = local;
-- the transactions that must not be lost
BEGIN;
SET LOCAL synchronous_commit = remote_apply;
INSERT INTO payments ...;
COMMIT;
The cost is paid only by the transactions that need it. On a system where 1% of commits are financially significant, that is a 1% cost instead of a 9% one.
What to take from this
synchronous_commit = onalone changes nothing.synchronous_standby_namesis what makes replication synchronous.remote_write/on/remote_applybuy standby-OS, standby-disk and standby-visibility guarantees respectively.- Measured on a loopback pair: 9% throughput cost for remote flush. Over a real network the round trip dominates. Measure yours.
- One synchronous standby makes you less available than none. Use
ANY 1 (s1, s2). - Alert on
sync_state, and rehearse dropping to asynchronous as an explicit decision. synchronous_commitis per-transaction. Pay the cost only where it buys something.
Cross-course references
- Linux for Production Sysadmins — Part LII (High availability fundamentals) and Part LIII (Quorum and split brain) cover the same trade between durability and availability, and why a quorum of one is a single point of failure wearing a quorum’s clothes.
- Observability for Production Sysadmins — Part LIX (Database
observability) covers alerting on
sync_state, because a cluster silently demoted to asynchronous reports nothing.
Quiz
Knowledge check · 6 questions
Q1. A team sets synchronous_commit = on and reports that replication is now synchronous. What have they actually changed?
Q2. With synchronous_standby_names = 'standby1' and that standby stopped for maintenance, what does the primary do?
Q3. During a synchronous stall an operator cancels waiting backends one at a time. Why is that the wrong approach?
Q4. Which are sound responses to a synchronous replication stall? Select all that apply.
Q5. synchronous_commit can be set per transaction, so only the transactions that need the guarantee pay for it.
Q6. The benchmark in this lesson showed a 9% throughput cost for remote flush. Why must that number not be quoted as the cost of synchronous replication?
Passing score: 75%. Answers are checked in this browser.