Replication
Streaming replication
A standby server is a physical (block-level) copy of the primary that stays current by receiving the primary's WAL over a network connection and replaying it, just like continuous crash recovery.
In plain words
A standby (replica) starts life as a copy of the primary's data directory. From then on it keeps up by doing what crash recovery does, but forever: it receives every WAL record the primary writes and replays it onto its own copy of the data.
Two processes do the transport. On the primary a WAL sender reads new WAL and pushes it down a network connection; on the standby a WAL receiver writes it to the standby's own pg_wal. The standby's startup process then replays it. Usually the delay is a fraction of a second.
If the primary fails, you promote the standby and it becomes the new primary. Standbys can also answer read-only queries (hot standby) and can feed further standbys (cascading).
For DBAs and operators
Setting one up
-- on the primary
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD '…';
-- pg_hba.conf: host replication replicator 10.0.0.0/24 scram-sha-256
$ pg_basebackup -h primary -U replicator -D /var/lib/postgresql/18/main \
-R -X stream -C -S standby1 -P-R creates the file standby.signal and writes primary_conninfo (plus primary_slot_name with -S) into postgresql.auto.conf. -C -S creates a replication slot so the primary keeps WAL the standby still needs.
Real start-up log of a new standby (PostgreSQL 18, excerpt)
LOG: entering standby modeLOG: redo starts at 0/3000028LOG: consistent recovery state reached at 0/3000120LOG: database system is ready to accept read-only connectionsLOG: started streaming WAL from primary at 0/4000000 on timeline 1Monitoring
-- on the primary
SELECT application_name, state, sync_state,
sent_lsn, write_lsn, flush_lsn, replay_lsn,
write_lag, flush_lag, replay_lag,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) AS replay_gap
FROM pg_stat_replication;
-- on the standby
SELECT status, flushed_lsn, sender_host, slot_name FROM pg_stat_wal_receiver;
SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn(),
now() - pg_last_xact_replay_timestamp() AS replay_delay;sent→write→flush→replay: a gap between sent and flush points to network or standby disk; flush far ahead of replay means replay is stuck, typically on a recovery conflict wait or a single-threaded replay bottleneck.- The
*_lagcolumns are times measured from the standby's feedback messages (sent after writes and flushes, and at least everywal_receiver_status_interval); they become NULL on an idle system. now() - pg_last_xact_replay_timestamp()grows on an idle primary even with no real lag.
If the standby falls too far behind without a slot, the primary may have recycled the WAL it needs:
FATAL: could not receive data from WAL stream: ERROR: requested WAL segment 000000010000000000000041 has already been removedIt can then only continue with restore_command from an archive, or must be rebuilt.
Promotion and failover
SELECT pg_promote(); (or pg_ctl promote) stops the walreceiver, finishes replaying what was received, selects a new timeline and opens read-write:
PostgreSQL itself does not detect failure or fence the old primary; tools such as Patroni do. The old primary can rejoin as a standby after pg_rewind (needs wal_log_hints or data checksums) or a fresh base backup.
For developers: inside the source
Where it lives
primary: WalSndLoop(XLogSendPhysical) replication/walsender.c
WALRead() up to GetFlushRecPtr() → CopyData to standby
ProcessRepliesIfAny() → ProcessStandbyReplyMessage()
standby: WalReceiverMain() replication/walreceiver.c
XLogWalRcvProcessMsg() → XLogWalRcvWrite() → XLogWalRcvFlush()
WakeupRecovery() → startup process replays
XLogWalRcvSendReply() (write/flush/apply positions)walsender.c:exec_replication_command()parses the replication protocol (grammar inrepl_gram.y/repl_scanner.l);START_REPLICATIONrunsStartReplication(), which entersWalSndLoop(XLogSendPhysical). A cascading standby sends up toGetStandbyFlushRecPtr()instead ofGetFlushRecPtr().ProcessStandbyReplyMessage()stores the reported positions, computes the*_lagtimes withLagTrackerRead()from samples thatXLogSendPhysical()recorded withLagTrackerWrite(), releases synchronous-commit waiters and advances a physical slot viaPhysicalConfirmReceivedLocation().walreceiver.c:WalReceiverMain(). After each flush it publishesWalRcv->flushedUpto, callsWakeupRecovery()and, if cascading is allowed,WalSndWakeup().XLogWalRcvSendHSFeedback()sends thehot_standby_feedbackxmin.libpqwalreceiver.c: the libpq transport (libpqrcv_connect(),libpqrcv_startstreaming(),libpqrcv_receive()), loaded as a module so the server binary does not link libpq; seeREADME.xlogrecovery.c:WaitForWALToBecomeAvailable(), the startup process's state machine that cycles betweenXLOG_FROM_ARCHIVE,XLOG_FROM_PG_WALandXLOG_FROM_STREAM, and callsRequestXLogStreaming()(walreceiverfuncs.c) to have the postmaster start a walreceiver.CheckForStandbyTrigger()notices promotion:pg_promote()inxlogfuncs.cwrites thepromotesignal file and sendsSIGUSR1to the postmaster.
Shared state
WalSnd (include/replication/walsender_private.h), one per walsender in WalSndCtl->walsnds[], holds sentPtr, write/flush/apply, writeLag/flushLag/applyLag and sync_standby_priority under a per-entry spinlock; pg_stat_get_wal_senders() copies it into pg_stat_replication. On the standby, WalRcvData (walreceiver.h: walRcvState, receiveStart, flushedUpto, latestWalEnd) backs pg_stat_wal_receiver. Walsenders do not poll: XLogFlush() and XLogBackgroundFlush() call WalSndWakeupProcessRequests(), which (through WalSndWakeup()) broadcasts the condition variable WalSndCtl->wal_flush_cv.
Experiment
- TAP tests in
src/test/recovery/t/:001_stream_rep.pl,004_timeline_switch.pl,055_cascade_reconnect.pl.PostgreSQL::Test::Clustergives youinit_from_backup(…, has_streaming => 1),wait_for_catchup()andpromote()to build your own scenarios. log_replication_commands = onlogs every command a walsender receives (IDENTIFY_SYSTEM,START_REPLICATION…).- Protocol reference:
doc/src/sgml/protocol.sgml(streaming replication protocol section), including the reply and hot-standby-feedback message layouts.
Parameters that shape this
primary_conninfo primary_slot_name max_wal_senders wal_level hot_standby wal_receiver_status_interval wal_sender_timeout wal_receiver_timeout wal_keep_size recovery_min_apply_delay wal_retrieve_retry_interval
Related topics
WAL sender WAL receiver Startup process Synchronous replication Replication slots Hot standby and recovery conflicts Archiving and point-in-time recovery Write-ahead log (WAL)