PostgreSQL Mastery
PostgreSQL Mastery lessons / Module 35: Replication

Streaming replication

Level 7: Operations and HA30 min

A physical standby continuously receives the primary's WAL and replays it, staying a byte-identical copy that can serve read-only queries and take over on failure. Replication slots, synchronous commit and lag monitoring are the controls that matter.

This lesson is interactive. Run every example on a real PostgreSQL 18 server in your browser, read EXPLAIN ANALYZE plans, and take the quiz.
Open the interactive lesson
You will learn to
  • Explain how WAL sender and receiver keep a standby in sync
  • Choose between asynchronous and synchronous replication and the synchronous_commit levels
  • Monitor lag and manage replication slots safely

conceptThe concept

Open the interactive lesson to use this diagram.

A standby is created from a base backup and started with standby.signal and primary_conninfo. Its WAL receiver connects to a WAL sender on the primary and streams WAL, which the startup process replays. With hot_standby = on it serves read-only queries.

By default replication is asynchronous: commits return when WAL is flushed locally, so a failover can lose the last moments of transactions. synchronous_standby_names makes commits wait for standby confirmation; synchronous_commit picks the level: remote_write, on (flushed on the standby) or remote_apply (visible on the standby, for read-your-writes).

A replication slot makes the primary keep WAL until the standby has received it, so a disconnected standby can catch up. The danger: an abandoned slot retains WAL forever and fills the disk; cap it with max_slot_wal_keep_size.

whyWhy it matters

Replication provides high availability, read scaling and a hot copy for upgrades and migrations. Misconfigured, it causes full disks (stale slots), lost transactions (async failover) or cancelled reports on the standby (query conflicts).

Use caseReal-world scenario

The shop runs a primary and two standbys in different availability zones: one synchronous with ANY 1 (s1, s2) for zero data loss, and reporting queries on the other with hot_standby_feedback considered carefully.

Try itExamples

Run and edit these in the interactive lesson.

Primary and standby configuration
# primary: postgresql.conf
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
max_slot_wal_keep_size = '50GB'
synchronous_standby_names = 'ANY 1 (standby_a, standby_b)'

# primary: pg_hba.conf
hostssl replication replicator 10.0.0.0/16 scram-sha-256

# standby: clone and configure in one step
pg_basebackup -h primary -U replicator -D /var/lib/postgresql/18/main \
  -X stream -R --slot=standby_a -C -P
Replication health on the primary
SELECT application_name, state, sync_state,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) AS replay_lag_bytes,
       write_lag, flush_lag, replay_lag
FROM pg_stat_replication;

SELECT slot_name, slot_type, active, wal_status,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots;
No rows here: the playground server has no standbys. On a real primary, an inactive slot with growing retained_wal is an incident in progress.
Am I a primary or a standby?
SELECT pg_is_in_recovery() AS is_standby,
       current_setting('synchronous_commit') AS synchronous_commit,
       current_setting('hot_standby') AS hot_standby;

internalsUnder the hood

Long queries on a standby can conflict with WAL replay (for example, VACUUM on the primary removed rows the query still needs). The standby waits up to max_standby_streaming_delay and then cancels the query. hot_standby_feedback avoids that by telling the primary about the standby's oldest snapshot, at the cost of bloat on the primary.

Avoid / preferDo this, not that

Avoid
SELECT pg_create_physical_replication_slot('standby_b');
-- standby_b is decommissioned; the slot stays
Prefer
max_slot_wal_keep_size = '100GB'
-- and alert on inactive slots

An inactive slot makes the primary keep WAL forever, until the disk fills and the primary stops.

Avoid
hot_standby_feedback = on  # everywhere, by default
Prefer
-- enable it only on replicas serving long queries, and watch primary bloat

Feedback stops query cancellations on the standby by holding back VACUUM on the primary, which can bloat busy tables.

PitfallsCommon mistakes

  • Leaving a replication slot for a decommissioned standby. The primary keeps all WAL until the disk is full.
  • Assuming synchronous_commit = on with a synchronous standby means reads on that standby see the commit. Only remote_apply guarantees that.
  • Using a single synchronous standby with no alternative. When it goes down, every commit on the primary waits.

LimitsWhen not to use it

A replica is not a backup: a DROP TABLE or bad UPDATE reaches it within milliseconds. Keep PITR backups alongside replicas.

SecuritySecurity notes

Replication connections stream every change. Use a dedicated role with the REPLICATION attribute, restrict it in pg_hba.conf to standby addresses, and require TLS with certificate verification.

ChallengeTry it yourself

List replication slots with their type, whether they are active and how much WAL each is holding back.

Show one solution
One solution
SELECT slot_name, slot_type, active,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots;

InterviewQuestions you should be able to answer

Asynchronous versus synchronous replication in PostgreSQL?
Asynchronous: the primary commits after its local WAL flush; standbys apply shortly after, so failover can lose recently committed transactions (RPO > 0) but commits are fast. Synchronous: commits wait for one or more standbys listed in synchronous_standby_names to confirm (write, flush or apply depending on synchronous_commit), giving RPO = 0 at the cost of latency and of availability if no synchronous standby is reachable. Quorum syntax ANY n (...) reduces that risk.
What are replication slots and what is their risk?
A slot records how far a consumer (physical standby or logical subscriber) has received, so the primary retains WAL (and for logical slots, catalog rows) until then. It prevents a lagging standby from falling off. The risk is an inactive or abandoned slot retaining WAL without limit and filling the disk, and holding back xmin which stops VACUUM. Monitor pg_replication_slots and set max_slot_wal_keep_size; since 18, idle_replication_slot_timeout can invalidate long-idle slots.

QuizCheck your understanding

4 questions with instant feedback in the interactive lesson; a wrong answer goes into your review deck.

Go deeperOfficial documentation