PostgreSQLXIV · Replication, Slots and Read ReplicasReplication
Replication slots and unbounded WAL retention
What you'll learn
- Explain what a slot guarantees and what it costs
- Bound slot retention deliberately and know what that trades away
- Recognise an invalidated slot from both sides
- Monitor slots so the failure is seen before the disk fills
Prerequisites
Practice
Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27
A replication slot is a promise the primary makes: I will keep every WAL segment you have not confirmed, for as long as it takes. That promise is what makes a standby reliably resumable, and it is also the most direct route to a full disk that PostgreSQL offers.
What a slot is
A slot records a position — restart_lsn — and the primary refuses to
recycle any segment from that position onward.
Without a slot, a standby that falls behind further than the primary’s WAL retention gets:
FATAL: requested WAL segment 000000010000000000000023 has already been removed
and must be rebuilt or fetched from the archive. With a slot, that cannot happen. The cost is that “cannot happen” is implemented by retaining WAL indefinitely.
$ SELECT slot_name, active, restart_lsn,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS wal_retained
FROM pg_replication_slots; slot_name | active | restart_lsn | wal_retained
-----------+--------+-------------+--------------
demo_slot | f | 0/39247A98 | 169 MB
standby1 | t | 0/43BC14E0 | 0 bytes169 MB pinned by a slot no standby has ever used, while the slot with a live standby attached holds nothing — because that standby is keeping up. A healthy slot retains almost nothing. Retention is the symptom.
At the 4,359 kB/s generation rate from lesson XII-02, an unattended reserving slot accumulates roughly 367 GB a day, indefinitely, until the filesystem fills and the primary stops committing.
Bounding it
ALTER SYSTEM SET max_slot_wal_keep_size = '100GB';
SELECT pg_reload_conf();
Beyond that limit, the slot is invalidated rather than allowed to keep growing.
$ SELECT slot_name, active, wal_status, pg_size_pretty(safe_wal_size) AS safe FROM pg_replication_slots; slot_name | active | wal_status | safe
-----------+--------+------------+-------
standby1 | f | reserved | 56 MB
round 1: wal_status: reserved safe_wal_size: 28 MB
round 2: wal_status: lost safe_wal_size: NULL
round 3: wal_status: lost safe_wal_size: NULLsafe_wal_size is the column to alert on. It is how many bytes of
WAL can still be generated before this slot is invalidated. It went 56 MB
→ 28 MB → NULL, and NULL means it is already too late.
$ docker logs rbpg-prim 2>&1 | grep -i invalidatLOG: invalidating obsolete replication slot "standby1"
DETAIL: The slot's restart_lsn 0/1C82A100 exceeds the limit by 8216320 bytes.
HINT: You might need to increase "max_slot_wal_keep_size".$ grep FATAL /tmp/sb.logFATAL: could not start WAL streaming: ERROR: can no longer access replication slot "standby1"
DETAIL: This replication slot has been invalidated due to "wal_removed".The standby could not resume and had to be rebuilt from a new base backup.
The abandoned slot
The classic outage, and it is always the same sequence:
- A standby is built, with a slot.
- The standby is decommissioned, fails, or is rebuilt with a new slot name.
- Nobody drops the old slot.
- WAL accumulates at the full generation rate.
- Days or weeks later,
pg_walfills and the cluster stops.
It is silent throughout. Nothing about the primary is unhealthy and nothing reports it.
-- run this on a schedule; alert on any row
SELECT slot_name, slot_type, active, wal_status,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn))
AS retained,
pg_size_pretty(safe_wal_size) AS safe_remaining
FROM pg_replication_slots
WHERE NOT active
OR wal_status IN ('extended','unreserved','lost');
What to take from this
- A slot guarantees a standby can resume, by retaining WAL without bound.
- A slot with a NULL
restart_lsnreserves nothing. - A healthy slot retains almost nothing. Retention is the symptom.
safe_wal_sizecounts down to invalidation. NULL means it already happened.max_slot_wal_keep_sizechooses which failure you get. A finite limit plus an archive is the arrangement that makes it safe.- Alert on inactive slots and on
wal_statusleavingreserved. - Logical slots additionally hold
catalog_xminand block catalogue vacuum cluster-wide.
Cross-course references
- Observability for Production Sysadmins — Part LIX (Database
observability) covers alerting on inactive slots and on
wal_status, which is the earliest signal available before the volume fills. - Linux for Production Sysadmins — Part XIV (Filesystems) covers the filesystem this eventually fills, and Part LXXX (Common failure scenarios) covers the class of failure where a safety mechanism is the cause.
Quiz
Knowledge check · 6 questions
Q1. pg_wal on a primary has grown to 400 GB. pg_replication_slots shows one slot with active = false and a restart_lsn from three weeks ago. What is happening?
Q2. A standby is stopped for two hours of host maintenance. When it returns it reports 'can no longer access replication slot' with detail 'invalidated due to wal_removed'. What happened, and what is the fix?
Q3. Why is an abandoned logical replication slot more damaging than an abandoned physical one?
Q4. Which conditions in pg_replication_slots warrant an alert? Select all that apply.
Q5. Creating a physical replication slot without immediately_reserve causes the primary to begin retaining WAL straight away.
Q6. Explain what max_slot_wal_keep_size actually decides, and how to make setting it safe.
Passing score: 75%. Answers are checked in this browser.