Diagnose physical-replication lag as a pipeline problem across transport, durable receive, replay, conflicts, and retention—then place slots and cascades with explicit responsibility.

Cascading Replication, Replication Slots, WAL Retention, and Replica Lag Diagnosis

Diagnose physical-replication lag as a pipeline problem across transport, durable receive, replay, conflicts, and retention—then place slots and cascades with explicit responsibility.

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 now has a direct standby, but operators need a more scalable topology and a reliable lag playbook. The dangerous shortcut is a single dashboard value called “replica lag.” Physical replication is a pipeline: generation → sender → network → receiver write → receiver flush → replay → query visibility. Different failures separate different stages.

01

Explain cascading replication and its direct-connection monitoring boundary.

02

Place physical slots on the upstream node that owns WAL-retention responsibility.

03

Calculate byte/LSN distance separately for send, flush, and replay.

04

Distinguish network, disk, replay, conflict, and workload lag signatures.

05

Use slot wal_status/safe_wal_size/inactive_since and cascading topology evidence before taking remediation action.

Important PostgreSQL behavior

Cascading physical replication is asynchronous. Primary synchronous replication settings do not make downstream cascade nodes part of the primary’s synchronous commit quorum.

1. Cascading replication changes who sends WAL

Instead of every standby connecting to the primary, a downstream standby can stream from an upstream standby. The upstream standby must accept replication connections and have enough max_wal_senders. The downstream's primary_conninfo points to the upstream standby, not the original primary.

text · conceptual cascading topology
primary:55436  └── standby_a:55437  (streams directly from primary)        └── standby_b:55438  (streams from standby_a)
text · standby_b recovery connection pattern
primary_conninfo = 'host=localhost port=55437 user=ch14_repl application_name=servicehub_cascade2'primary_slot_name = 'ch14_cascade_slot'

On the primary, pg_stat_replication shows standby_a only. On standby_a, its own pg_stat_replication shows standby_b. This monitoring boundary matters during incidents: absence from the primary view does not mean a cascade does not exist.

2. Slot placement follows the sender that must retain WAL

If standby_b streams from standby_a, the slot protecting standby_b's required WAL belongs on standby_a. A slot on the original primary cannot force standby_a to retain downstream WAL once it is the sender.

sql · upstream standby: create/inspect downstream physical slot
SELECT * FROM pg_create_physical_replication_slot('ch14_cascade_slot');SELECT slot_name, active, restart_lsn, wal_status,       safe_wal_size, inactive_since, invalidation_reasonFROM pg_replication_slotsWHERE slot_name='ch14_cascade_slot';

Physical slots can exist on a standby where supported by the current configuration/feature set, but your topology must preserve them across promotion or recreate the retention relationship deliberately. Do not assume “slot exists somewhere” protects every chain segment.

3. Measure byte distance, not only interval lag

sql · primary: byte distances for each directly connected standby
SELECT application_name, state, sync_state,       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn))   AS not_sent,       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), write_lsn))  AS not_written_remote,       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), flush_lsn))  AS not_flushed_remote,       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) AS not_replayed_remote,       write_lag, flush_lag, replay_lagFROM pg_stat_replication;

Byte distance tells you how much WAL separates positions. Lag intervals tell you how long recent acknowledgment stages took. PostgreSQL explicitly warns that write_lag/flush_lag/replay_lag are not catch-up-time predictions.

4. Diagnose by pipeline signature

Observed shape Likely area Next evidence
sent_lsn far behind current WAL sender/network/backpressure sender wait events, network, receiver connectivity
sent current, write/flush behind standby receive/storage pg_stat_wal_receiver, standby I/O, filesystem latency
flush current, replay far behind recovery apply conflicts, CPU/I/O, recovery prefetch, WAL workload
replay nearly current, timestamp “lag” old while idle no recent replayed commits LSN equality and workload activity
slot restart_lsn increasingly old consumer retention slot active state, consumer health, safe_wal_size, disk
sql · standby: receiver and replay diagnostic bundle
SELECT status, written_lsn, flushed_lsn, latest_end_lsn,       last_msg_send_time, last_msg_receipt_time,       sender_host, sender_port, slot_nameFROM pg_stat_wal_receiver;SELECT pg_last_wal_receive_lsn(),       pg_last_wal_replay_lsn(),       pg_last_xact_replay_timestamp();SELECT datname, confl_snapshot, confl_lock, confl_bufferpin,       confl_deadlock, confl_tablespaceFROM pg_stat_database_conflicts;

5. Replay conflict versus slow I/O

A large receive-to-replay gap plus increasing pg_stat_database_conflicts suggests recovery conflicts. The same gap without conflict growth can instead come from replay CPU, storage, full-page images, index maintenance, or a bursty primary workload. Correlate with standby wait events and I/O metrics; do not “solve” every replay gap by enabling feedback.

sql · standby: relevant backend wait events
SELECT pid, backend_type, state, wait_event_type, wait_eventFROM pg_stat_activityWHERE backend_type IN ('startup','walreceiver')ORDER BY backend_type;

6. Slot danger states in PostgreSQL 18

wal_status can move through reserved, extended, unreserved, and lost depending on configured retention limits and WAL removal. safe_wal_size estimates how many more bytes may be written before the slot risks becoming lost when a finite max_slot_wal_keep_size applies. inactive_since shows when use stopped.

sql · slot retention alert query
SELECT slot_name, slot_type, active, restart_lsn,       wal_status,       pg_size_pretty(safe_wal_size) AS safe_wal_remaining,       inactive_since, invalidation_reasonFROM pg_replication_slotsWHERE slot_type='physical'ORDER BY active, inactive_since NULLS LAST;

If wal_status='lost', that slot can no longer supply its required WAL. Reinitializing the standby from a fresh base backup is often safer than trying to improvise around missing physical WAL.

7. Cascading failure semantics

If the primary is healthy but standby_a fails, standby_b loses its upstream even though the original primary still runs. A well-designed topology documents whether standby_b can be re-pointed to another upstream, whether its needed WAL is available there, and how recovery timelines will be followed after promotion. recovery_target_timeline='latest' is the normal failover-friendly behavior.

Wrong approach

A single “replica lag = 3s” alert hides where the pipeline is stuck and can trigger the wrong fix. Collect positions, byte distances, receive/replay state, conflicts, slots, and infrastructure evidence. Also monitor every hop of a cascade; the primary cannot report downstream standbys it does not directly serve.

8. Retention runway is a rate problem

A slot's retained byte distance becomes operationally urgent when combined with the current WAL generation rate and free disk. Ten gigabytes of retained WAL can be harmless on one system and minutes from failure on another. Measure generation over representative intervals and derive a runway; do not alert on a universal retained-byte threshold.

psql · primary or upstream: measure WAL generated over an interval
SELECT pg_current_wal_insert_lsn() AS start_lsn \gset\echo 'Run the representative workload for a measured interval.'SELECT pg_size_pretty(         pg_wal_lsn_diff(pg_current_wal_insert_lsn(), :'start_lsn'::pg_lsn)       ) AS generated_wal;

Combine this with filesystem free space and slot safe_wal_size. If a consumer is intentionally offline, document how long it can remain offline before rebuild becomes preferable to continued retention.

9. Cascading reduces primary fan-out but adds failure domains

Cascading can reduce the primary's connection count and inter-site bandwidth, but downstream availability now depends on the upstream relay. A deep chain also compounds replay delay: downstream cannot receive WAL before the upstream has received enough to stream it onward. Keep chain depth justified by topology, not aesthetics.

When an upstream standby is promoted, downstream nodes can follow its new timeline when configured to follow the latest timeline. When a different node is promoted, downstream connection strings may need rerouting. Test both planned switchover and unplanned failover paths rather than assuming the cascade will rewire itself.

10. Differentiate lag from data loss risk

Lag is an observed distance in the current stream. Data-loss risk depends on the durability contract and which candidate would be promoted. An asynchronous standby can have zero current lag but still offer no guarantee that every future acknowledged primary commit will reach it before a sudden primary crash. Conversely, a synchronous standby can be temporarily replay-lagged while still having durably flushed acknowledged WAL. Keep “RPO protection” and “query freshness” as separate dashboards.

sql · primary: durable receipt versus query visibility
SELECT application_name,       pg_wal_lsn_diff(flush_lsn, replay_lsn) AS flushed_not_replayed_bytes,       sync_state, flush_lag, replay_lagFROM pg_stat_replicationWHERE flush_lsn IS NOT NULL AND replay_lsn IS NOT NULL;

11. Production lag runbook

When lag alarms fire: confirm the primary is generating WAL; identify directly connected nodes at each hop; compare send/write/flush/replay positions; check receiver connectivity; inspect standby replay conflicts and I/O; inspect slot retention; estimate disk runway from measured WAL generation; and only then choose remediation. Common fixes include restoring network/storage capacity, ending pathological standby reads, tuning workload-specific feedback/delay policy, or rebuilding a lost standby.

Check your understanding

  1. Why is a cascade not visible in the original primary’s pg_stat_replication?
  2. Where should a physical slot for a downstream cascade normally live?
  3. Why are replay_lag intervals not catch-up ETA?
  4. What does wal_status=lost mean operationally?
  5. What pattern suggests WAL is received but recovery cannot apply it quickly?
Review the answers

Only direct WAL sender connections appear in each node’s pg_stat_replication. The sender/upstream that must retain WAL owns the downstream slot. Lag intervals describe recent acknowledgement delay, not future catch-up rate. lost means required WAL is gone and the slot is unusable. A small receive/flush gap with a large replay gap points to apply/conflict/standby-resource issues.

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.