Skip to main content
RunBook Academy

PostgreSQLXIV · Replication, Slots and Read ReplicasReplication

Replication slots and unbounded WAL retention

Advanced⏱ ~30 min🧪 Lab requiredpsql

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

Not yet marked complete on this device.

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.

Read-only / Safea reserving slot with no consumer
$ 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 bytes

169 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.

Read-only / Safesafe_wal_size counting down to invalidation
$ 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: NULL

safe_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.

Service impact possiblethe primary, at the moment of invalidation
$ docker logs rbpg-prim 2>&1 | grep -i invalidat
LOG:  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".
Service impact possiblethe standby, when it came back
$ grep FATAL /tmp/sb.log
FATAL:  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:

  1. A standby is built, with a slot.
  2. The standby is decommissioned, fails, or is rebuilt with a new slot name.
  3. Nobody drops the old slot.
  4. WAL accumulates at the full generation rate.
  5. Days or weeks later, pg_wal fills 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_lsn reserves nothing.
  • A healthy slot retains almost nothing. Retention is the symptom.
  • safe_wal_size counts down to invalidation. NULL means it already happened.
  • max_slot_wal_keep_size chooses which failure you get. A finite limit plus an archive is the arrangement that makes it safe.
  • Alert on inactive slots and on wal_status leaving reserved.
  • Logical slots additionally hold catalog_xmin and 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

  1. 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?

  2. 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?

  3. Q3. Why is an abandoned logical replication slot more damaging than an abandoned physical one?

  4. Q4. Which conditions in pg_replication_slots warrant an alert? Select all that apply.

  5. Q5. Creating a physical replication slot without immediately_reserve causes the primary to begin retaining WAL straight away.

  6. 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.