Replication
// the problem
One server is a single point of failure and a single source of read capacity. Replication fixes both: the WAL you met earlier isn't just for crash recovery — stream it to another machine, replay it there, and you have a live, read-only standby that can take over if the primary dies. The only question that really matters is: when the primary fails, how much of the newest data might be gone? That's the sync-vs-async trade-off.
Write on the primary, stream the WAL to the standby, and watch the lag. Then pull the plug — promote the standby — in async vs sync mode and see what survives.
primary
accepts writes → WAL
no writes yet
standby
replays WAL · read-only
nothing replayed yet
async: the primary commits immediately; the standby trails by the lag — a failover loses the un-streamed tail
It's just WAL, shipped
Physical (streaming) replication reuses the machinery from the WAL lesson: every change is already a WAL record on the primary. A WAL sender process on the primary streams those records to a WAL receiver on the standby, which replays them into its own copy of the data files — a byte-for-byte clone that trails by the replication lag, usually milliseconds.
There are actually four positions to track, because a record moves through
stages on the standby: it's sent, then written to the standby's
WAL, then flushed (fsync'd), then replayed into the data files.
pg_stat_replication on the primary shows all four per standby — sent_lsn,
write_lsn, flush_lsn, replay_lsn — and lag is simply pg_current_wal_lsn()
minus the standby's replay_lsn.
Standbys are hot standbys: fully queryable, read-only. So replication buys you two things at once — high availability (promote a standby on failure) and read scaling (send read traffic to replicas).
Async vs sync: the data-loss dial
The dial is when a commit is allowed to return:
- Asynchronous (the default): the primary commits and returns immediately, and the standby catches up a moment later. Fast, but if the primary dies before a commit has streamed, promoting the standby loses that commit. You saw the gap grow as lag in the explorer.
- Synchronous: a commit waits until at least one standby confirms it has the WAL record, then returns. Zero committed data lost on failover — at the cost of round-trip latency on every commit (and it stalls if the sync standby is down).
// why it matters · the same synchronous_commit dial, now spanning machines
synchronous_commit picks how far a commit waits before returning. In the WAL
lesson it was local; with a synchronous standby it extends across the network, and
each level waits for a later point on the standby:
remote_write— the standby has received the WAL into memory (survives a Postgres crash there, but not an OS crash).on— the standby has flushed (fsync'd) the WAL to disk. The usual "sync replication" setting: no committed data lost even if the standby's OS crashes.remote_apply— the standby has replayed it, so a read routed to that standby is guaranteed to see the commit (read-your-writes across replicas).
Which standbys count is set by synchronous_standby_names, including quorum rules
like ANY 2 (s1, s2, s3). Most systems run async and accept a tiny loss
window; go sync only when losing even one committed transaction is unacceptable.
Replication slots keep the WAL around
A standby that falls behind (or disconnects) still needs the WAL it hasn't
consumed yet. A replication slot makes the primary retain WAL until the
standby confirms it — so a lagging replica can always catch up. Create one and
inspect it right here; with no standby attached it shows active = false —
exactly the shape of a forgotten slot:
// gotcha · a forgotten slot fills your disk
That active = false is the danger. A slot for a standby that's gone forever
pins WAL forever: the primary can't recycle it, so pg_wal grows until the
disk fills — a classic outage. Monitor pg_replication_slots (watch wal_status
slide from reserved toward lost) and drop dead ones.
wal_level must be high enough to log the detail replication needs, and these are
the views you'd watch on a real primary:
Logical replication: rows, not bytes
Physical replication copies the whole cluster byte-for-byte. Logical replication instead decodes the WAL into row-level changes (INSERT / UPDATE / DELETE). The publisher declares a publication; a subscriber elsewhere creates a subscription that applies those changes. Declare one live:
Because it ships rows instead of bytes, logical replication can target
selected tables, replicate between different major versions, or feed an
entirely different system — the foundation of change-data-capture and
zero-downtime major-version upgrades. It needs wal_level = logical, and each
published table needs a replica identity (its primary key by default) so the
subscriber can match rows for UPDATE/DELETE.
Your turn
Show this server's WAL level — it must be at least 'replica' to feed standbys.
How many standbys are currently streaming from this server? (Zero here — no replicas are connected.)
// what you now understand
- 01A WAL sender on the primary streams records to a WAL receiver on the standby, which replays them into a byte-for-byte hot standby (queryable, read-only) — high availability + read scaling.
- 02A record moves sent → written → flushed → replayed on the standby; pg_stat_replication shows all four LSNs, and lag = pg_current_wal_lsn() − replay_lsn. A standby can be promoted on failure (failover).
- 03Async commits return immediately (a failover loses the un-streamed tail). Sync (synchronous_commit = remote_write / on / remote_apply) waits for the standby to receive / flush / replay — each stronger and slower; synchronous_standby_names picks which standbys count (incl. quorum ANY 2 of 3).
- 04Replication slots make the primary retain WAL until a standby confirms it (active=true). A forgotten slot (active=false) pins WAL forever and fills the disk — monitor wal_status and drop dead ones.
- 05wal_level must be 'replica' for physical replication, 'logical' for logical decoding; pg_stat_replication and pg_replication_slots show the state.
- 06Logical replication decodes WAL into row changes via publications/subscriptions (each table needs a replica identity) — replicate selected tables across major versions or into other systems: the basis of CDC and zero-downtime upgrades.
// self-test
You run asynchronous replication. The primary's disk dies, and you promote the standby. What's the risk?
// self-test
A replica was decommissioned but its replication slot was never dropped. Weeks later the primary's disk is full. Why?
// go deeper
- High Availability & Replication (docs) — streaming replication, standbys, failover
- Synchronous replication — the sync/async durability trade-off
- Replication slots — WAL retention (and the disk-fill risk)
- Logical replication — publications, subscriptions, CDC, upgrades