Replication
Synchronous replication
With synchronous replication a COMMIT only returns once one or more chosen standbys confirm they have the transaction's WAL. synchronous_commit decides how far that confirmation must go.
In plain words
Normal streaming replication is asynchronous: the primary says "committed" as soon as the change is safe on its own disk, and the standby catches up a moment later. If the primary dies in that moment, the last few transactions are lost.
Synchronous replication closes that gap. You name standbys in synchronous_standby_names, and each COMMIT waits until enough of them say "I have it". synchronous_commit says what "I have it" means: handed to the standby's operating system (remote_write), written to disk (on), or already visible to queries on the standby (remote_apply).
The price is latency and an availability trade-off: while the configuration still requires acknowledgements from unavailable synchronous standbys, commits can wait indefinitely in SyncRep until a suitable standby responds, the configuration changes, or the wait is interrupted. Pick the settings below and watch which acknowledgement COMMIT waits for.
For DBAs and operators
Naming the standbys
Each standby identifies itself with application_name in its primary_conninfo (default: cluster_name, else walreceiver). Forms of synchronous_standby_names:
's1'or's1, s2': the old form, same asFIRST 1 (s1, s2).FIRST 2 (s1, s2, s3): priority. The first two connected standbys in list order aresync; the rest arepotentialand take over automatically if one disappears.ANY 2 (s1, s2, s3): quorum. Any two acknowledgements are enough; all are shown asquorum.'*'matches any standby. An empty string (default) means asynchronous.
Only the primary reads it, changes need just a reload, and it can be changed at any time, which is how you "unblock" a hung cluster.
What COMMIT waits for
| synchronous_commit | local WAL flush | sync standbys |
|---|---|---|
off | no (walwriter flushes later) | no |
local | yes | no |
remote_write | yes | written to standby OS (survives a standby PostgreSQL crash, not an OS crash) |
on (default) | yes | flushed to standby disk |
remote_apply | yes | replayed, so visible to standby queries |
It can be set per session or per transaction: SET LOCAL synchronous_commit = local for a bulk load, remote_apply for read-your-writes on replicas.
Monitoring
SELECT application_name, state, sync_state, sync_priority,
write_lag, flush_lag, replay_lag
FROM pg_stat_replication;
-- sessions stuck waiting:
SELECT pid, wait_event, now() - xact_start AS waiting, query
FROM pg_stat_activity WHERE wait_event = 'SyncRep';LOG: standby "s1" is now a synchronous standby with priority 1LOG: standby "s2" is now a candidate for quorum synchronous standbyWhen the sync standby is gone
Commits hang in wait event SyncRep; the change is already durable locally and visible to nobody else until the wait ends. Cancelling the backend does not roll back:
Fixes: bring a standby back, list more candidates (FIRST 1 (s1, s2)), or as an emergency set synchronous_standby_names = '' and SELECT pg_reload_conf(). Tools like Patroni manage this automatically.
Synchronous replication does not make standbys read-your-writes consistent unless you use remote_apply, and it does not prevent split brain by itself.
For developers: inside the source
Where it lives
Almost everything is in syncrep.c, with the waiting side in the committing backend and the releasing side in the walsenders.
backend: RecordTransactionCommit() access/transam/xact.c
XLogFlush(XactLastRecEnd) local flush first
SyncRepWaitForLSN(XactLastRecEnd, true) replication/syncrep.c
SyncRepQueueInsert(mode); WaitLatch(…, WAIT_EVENT_SYNC_REP)
walsender: ProcessStandbyReplyMessage()
SyncRepReleaseWaiters()
SyncRepGetSyncRecPtr() → SyncRepWakeQueue(false, mode)SyncRepWaitForLSN()returns at once unlessSyncRepRequested()(max_wal_senders > 0andsynchronous_commitabovelocal) andWalSndCtl->sync_standbys_statushasSYNC_STANDBY_DEFINED. Otherwise, underSyncRepLock, it setsMyProc->waitLSN,syncRepState = SYNC_REP_WAITINGand links the PGPROC intoWalSndCtl->SyncRepQueue[mode], kept sorted by LSN. The mode isSYNC_REP_WAIT_WRITE,SYNC_REP_WAIT_FLUSHorSYNC_REP_WAIT_APPLYfromremote_write,onandremote_apply. Two-phase commits wait through the same function (twophase.c).SyncRepReleaseWaiters()runs in a walsender after each standby reply.SyncRepGetSyncRecPtr()picks the positions that are safe: forFIRST n(SYNC_REP_PRIORITY)SyncRepGetOldestSyncRecPtr()takes the minimum over the chosen standbys; forANY n(SYNC_REP_QUORUM)SyncRepGetNthLatestSyncRecPtr()takes the n-th highest. New values go intoWalSndCtl->lsn[mode];SyncRepWakeQueue()marks waitersSYNC_REP_WAIT_COMPLETEand sets their latches. The "is now a synchronous standby" log lines are printed here too.SyncRepGetCandidateStandbys()andSyncRepGetStandbyPriority()decide who is sync; each walsender's priority is stored inWalSnd.sync_standby_priority(shown assync_priority).
Configuration and cancellation
check_synchronous_standby_names() parses the GUC with a small bison grammar (syncrep_gram.y, syncrep_scanner.l) into SyncRepConfigData (num_sync, syncrep_method, member_names). The checkpointer owns the shared "is sync rep defined" flag: on reload it calls SyncRepUpdateSyncStandbysDefined(), and when the list becomes empty it wakes every queued backend, which is why emptying the setting unblocks hung commits.
In the wait loop, QueryCancelPending produces the "canceling wait for synchronous replication due to user request" WARNING and returns (the commit record is already flushed); ProcDiePending emits "canceling the wait for synchronous replication and terminating connection due to administrator command" and the backend exits. Neither can roll back.
Experiment
src/test/recovery/t/007_sync_rep.plcovers priority, quorum and*lists and checkssync_state.- Stop the synchronous standby and watch
pg_stat_activity.wait_event = 'SyncRep';pg_stat_replication.sync_stateshows the walsender view. - Docs source:
doc/src/sgml/high-availability.sgml, sectionsynchronous-replication.
Parameters that shape this
synchronous_standby_names synchronous_commit wal_sender_timeout wal_receiver_status_interval max_wal_senders application_name
Related topics
Streaming replication WAL sender WAL receiver Write-ahead log (WAL) WAL writer Replication slots Hot standby and recovery conflicts