Build a disposable PostgreSQL 18 primary/standby pair and trace physical replication from WAL generation through sender, receiver, durable receipt, replay, and slot-backed retention.

Primary/Standby Architecture, WAL Sender/Receiver, Replay, and Replication Slots

Build a disposable PostgreSQL 18 primary/standby pair and trace physical replication from WAL generation through sender, receiver, durable receipt, replay, and slot-backed retention.

Intermediate → Advanced180–240 minutesMulti-node physical replication and HA labCurrent patched PostgreSQL 18.xCore PostgreSQL utilities onlyReplication/owner/admin privileges where explicitly statedDisposable local nodes on ports 55436–55438Third-party HA orchestrators are conceptual onlyLast reviewed: August 2026

Learning outcomes

ServiceHub has reached the point where a single database process is not an acceptable availability boundary. The first instinct is often “add a replica,” but a physical PostgreSQL standby is not a second independent database that periodically copies rows. It is another PostgreSQL cluster that receives the primary's Write-Ahead Log (WAL) byte stream and replays the same physical changes against a base copy of the same cluster.

This lesson builds a disposable local pair so you can observe that pipeline. The primary runs on port 55436; the standby runs on 55437. Both use the same PostgreSQL 18 major version and free upstream utilities. These ports and directories are intentionally separate from the long-lived servicehub_lab used earlier in the course.

01

Explain physical replication as base-copy plus WAL transport/replay, not table-level copying.

02

Create a least-privilege replication login and host-based replication rule.

03

Bootstrap a standby with pg_basebackup and standby.signal.

04

Read pg_stat_replication, pg_stat_wal_receiver, LSN positions, and backend types without confusing receipt with replay.

05

Use a physical replication slot while monitoring its WAL-retention risk.

Topology vocabulary

A PostgreSQL database cluster is one server data directory containing multiple databases. In this chapter, “primary” and “standby” describe replication roles of two separate PostgreSQL clusters; they do not mean two databases inside one cluster.

1. The physical replication pipeline

A committing backend on the primary generates WAL. A WAL sender (walsender) process streams requested WAL to the standby. The standby's WAL receiver (walreceiver) receives and writes that stream. The standby's startup/recovery process then replays the WAL records into its local data files. These stages can be at different Log Sequence Numbers (LSNs), which is why “replication lag” is not one number.

sql · primary-side process and WAL position evidence
SELECT pg_current_wal_insert_lsn() AS insert_lsn,       pg_current_wal_lsn()        AS write_lsn,       pg_current_wal_flush_lsn()  AS flush_lsn;SELECT pid, backend_type, state, wait_event_type, wait_eventFROM pg_stat_activityWHERE backend_type IN ('walsender','walreceiver','startup')ORDER BY backend_type, pid;

On the primary you normally see a walsender once the standby connects. On the standby you see a walreceiver plus recovery/startup activity. Do not infer end-to-end replication health from process existence alone: the processes can be connected while replay is delayed.

2. Prepare a disposable primary

The lab requires PostgreSQL server utilities (initdb, pg_ctl, pg_basebackup, psql) from the same PostgreSQL 18 installation. Exact binary paths and service wrappers differ by Windows, Linux, macOS, and packages, so the examples use direct upstream-style commands. Run them only against disposable directories.

shell · initialize and start the disposable primary
initdb -D ./ch14_primarypg_ctl -D ./ch14_primary -o "-p 55436" -l ./ch14_primary.log start
text · primary settings to inspect or add
listen_addresses = 'localhost'port = 55436wal_level = replicamax_wal_senders = 10max_replication_slots = 10hot_standby = on# Optional retention guard for the lab; size according to measured WAL in real systems.max_slot_wal_keep_size = '1GB'

wal_level=replica is sufficient for physical streaming. max_wal_senders and max_replication_slots are capacity limits, not targets to maximize. Some settings require restart; check pg_settings.context and pending_restart instead of guessing.

sql · verify effective configuration and source
SELECT name, setting, unit, context, source, pending_restartFROM pg_settingsWHERE name IN ('wal_level','max_wal_senders','max_replication_slots',               'max_slot_wal_keep_size','hot_standby')ORDER BY name;

3. Create a least-privilege replication identity

Physical streaming uses the replication protocol. Create a login with the REPLICATION attribute rather than reusing a superuser. For a local lab, allow only loopback replication traffic in pg_hba.conf. Do not put the password on a process command line.

psql · create the replication login
CREATE ROLE ch14_repl LOGIN REPLICATION;\password ch14_repl
text · pg_hba.conf rule for local replication only
host    replication    ch14_repl    127.0.0.1/32    scram-sha-256host    replication    ch14_repl    ::1/128         scram-sha-256

Reload host-based authentication after editing it. A replication rule uses the special database field replication. Authentication succeeds or fails before any standby can receive WAL, so connection errors should be diagnosed separately from replay lag.

4. Bootstrap the standby from a physical base backup

A physical standby needs a physically compatible starting copy. pg_basebackup can create that copy while also arranging streaming recovery. -R writes standby.signal and connection settings. -C -S creates and names a physical slot on the primary. Use an interactive password prompt or a protected password file; the example intentionally omits a secret.

shell · take the standby base backup and create its slot
pg_basebackup \  -h localhost -p 55436 -U ch14_repl \  -D ./ch14_standby \  -Fp -Xs -P -R \  -C -S ch14_standby_slot -W
shell · start the standby on its own port
pg_ctl -D ./ch14_standby -o "-p 55437" -l ./ch14_standby.log start

-Xs streams WAL needed for the base backup. The standby remains read-only while in recovery. The physical base copy and WAL stream must correspond to the same cluster history; this is not a cross-major logical migration technique.

5. Observe sender, receiver, write, flush, and replay

sql · primary: inspect directly connected standby progress
SELECT pid, usename, application_name, client_addr, state,       sent_lsn, write_lsn, flush_lsn, replay_lsn,       write_lag, flush_lag, replay_lag,       sync_state, reply_timeFROM pg_stat_replication;

sent_lsn is how far this sender has transmitted. write_lsn is how far the standby reports writing to its operating system, flush_lsn is how far it reports flushing durably, and replay_lsn is how far recovery has applied changes. The lag-time columns describe recent acknowledgement delay; they are not forecasts of how long a badly lagged standby will need to catch up.

sql · standby: inspect WAL receiver and replay state
SELECT status, receive_start_lsn, written_lsn, flushed_lsn,       latest_end_lsn, latest_end_time, slot_name, sender_host, sender_portFROM pg_stat_wal_receiver;SELECT pg_is_in_recovery() AS standby,       pg_last_wal_receive_lsn() AS received,       pg_last_wal_replay_lsn() AS replayed,       pg_last_xact_replay_timestamp() AS last_replayed_commit;

written_lsn in pg_stat_wal_receiver is explicitly not a durability proof; flushed_lsn is the durable-receipt boundary reported by the receiver. Replay can still trail flush because CPU, I/O, conflicts, or long WAL records delay application.

6. Generate an observable ServiceHub change

psql · primary: create replicated ServiceHub data
CREATE DATABASE servicehub_ha_lab;\c servicehub_ha_labCREATE SCHEMA app;CREATE TABLE app.ch14_work_orders (  work_order_id bigint PRIMARY KEY,  status text NOT NULL,  changed_at timestamptz NOT NULL DEFAULT clock_timestamp());INSERT INTO app.ch14_work_orders VALUES (14001,'queued',clock_timestamp());SELECT pg_current_wal_flush_lsn() AS committed_through;
psql · standby: verify read-only replicated state
\c servicehub_ha_labSELECT pg_is_in_recovery(), * FROM app.ch14_work_orders;-- This should fail on a hot standby:INSERT INTO app.ch14_work_orders VALUES (14002,'should_fail',clock_timestamp());

The expected write failure is a read-only/recovery restriction, not evidence that replication is unhealthy. Physical replay has already reconstructed the primary's catalog and table state on the standby.

7. Physical slots solve one risk by creating another

A slot prevents the primary from recycling WAL that its consumer still needs. That protects a disconnected standby from immediately losing its place—but an abandoned or inactive slot can retain enormous WAL. PostgreSQL 18 exposes status and safety fields specifically so operators can monitor that risk.

sql · primary: monitor the physical slot
SELECT slot_name, slot_type, active, active_pid,       restart_lsn, wal_status, safe_wal_size,       inactive_since, invalidation_reasonFROM pg_replication_slotsWHERE slot_name = 'ch14_standby_slot';SELECT pg_size_pretty(         pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)       ) AS retained_distanceFROM pg_replication_slotsWHERE slot_name = 'ch14_standby_slot'  AND restart_lsn IS NOT NULL;
Wrong approach

Creating a physical slot and then treating it as permanent insurance is unsafe. If the standby is retired or disconnected for too long, the slot can retain WAL until pg_wal fills. Monitor active/inactive state, restart_lsn, wal_status, safe_wal_size, disk capacity, and a documented ownership/lifecycle process.

8. Verification and cleanup

Before calling the pair healthy, verify the standby is streaming, that byte distance is converging under a quiet workload, that a committed ServiceHub marker appears on the standby, and that the slot is active. Save logs and LSN evidence. Then stop and remove only these disposable clusters.

shell · clean shutdown of the disposable pair
pg_ctl -D ./ch14_standby stop -m fastpg_ctl -D ./ch14_primary stop -m fast# Delete ./ch14_standby and ./ch14_primary only after confirming they are the lab directories.

Check your understanding

  1. Why is physical replication not equivalent to copying SQL rows?
  2. What is the difference between flush_lsn and replay_lsn?
  3. Why can an inactive physical slot be dangerous?
  4. Why does pg_stat_replication not show downstream cascading standbys?
  5. Which role privilege is sufficient for a dedicated physical streaming login?
Review the answers

Physical replication replays WAL against a physical base copy. flush_lsn means the standby has durably received WAL; replay_lsn means recovery has applied it. An inactive slot can retain WAL and exhaust disk. pg_stat_replication reports directly connected WAL senders only. A login with the REPLICATION attribute plus an appropriate pg_hba.conf rule is sufficient for streaming; superuser is unnecessary.

Authoritative references

Replication and HA behavior is topology-, version-, privilege-, and operating-system-sensitive. These primary PostgreSQL sources define the mechanisms used in this lesson.

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.