Blog
4 min read

Postgres Replication Slots: Why an Inactive Slot Can Fill Your Disk

Replication slots guarantee a replica or CDC consumer never misses WAL — by keeping it until the consumer confirms it. When the consumer disappears, WAL piles up until the disk is full. How slots work, how to measure retained WAL, max_slot_wal_keep_size and idle timeouts, the xmin side effect, and safe cleanup.

A replication slot is a promise: "Postgres will keep every WAL segment this consumer hasn't confirmed yet." That's exactly what you want for a streaming replica or a change-data-capture pipeline — and exactly what fills your disk at 3 a.m. when the consumer goes away and nobody notices. (The Postgres WAL Explained)

Physical vs logical slots

  • Physical slots are used by streaming replicas. They retain WAL from the replica's restart_lsn, and optionally hold back the primary's xmin if hot_standby_feedback is on.
  • Logical slots are used by logical replication and CDC tools (Debezium, subscriptions). They decode WAL into row changes, retain WAL, and hold a catalog_xmin so the system catalogs needed for decoding aren't vacuumed away. (Change Data Capture in Postgres, Postgres Logical Replication)

How the disk fills up

Normally, Postgres recycles WAL segments after checkpoints once nothing needs them. A slot retains the segment containing its restart_lsn and the WAL after it: restart_lsn is the oldest WAL the consumer might still need. If the consumer is down — a decommissioned replica, a crashed Debezium connector, a test subscription someone forgot — pg_wal grows with the WAL your workload generates until the disk is full, unless retention limits intervene. When the WAL disk fills, Postgres stops. (PostgreSQL's slot documentation)

Measure it

SELECT slot_name,
       slot_type,
       active,
       wal_status,
       pg_size_pretty(
         pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)
       ) AS retained_wal,
       xmin,
       catalog_xmin
FROM pg_replication_slots
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC;
  • active = false for long periods is the red flag.
  • wal_status: reserved (fine), extended (retaining beyond max_wal_size), unreserved (about to lose WAL), lost (required WAL removed — the slot is unusable).
  • retained_wal: alert when it exceeds some fraction of free disk.

Also watch disk usage of the WAL directory directly; it's the symptom you'll see first. (Check Disk Space and Memory on Linux, No Space Left on Device)

Put a ceiling on it

max_slot_wal_keep_size (Postgres 13+) caps how much WAL slots may retain. Past the cap, Postgres removes WAL anyway and the slot becomes lost:

ALTER SYSTEM SET max_slot_wal_keep_size = '50GB';
SELECT pg_reload_conf();

The trade-off is explicit: a lagging consumer may have to be rebuilt from scratch, but the primary stays up. For almost every setup, losing a replica is better than losing the primary. The default (-1) means unlimited.

Postgres 18 also adds idle_replication_slot_timeout, which invalidates slots that have been inactive longer than a set time — another guard against forgotten slots.

The xmin side effect

Slots don't only retain WAL. A slot with an old xmin or catalog_xmin stops VACUUM from cleaning dead rows newer than that horizon, causing bloat — and in the extreme, blocks freezing and pushes you toward transaction ID wraparound. (Postgres Transaction ID Wraparound, Postgres VACUUM and Bloat)

Cleaning up safely

  1. Identify the owner of each inactive slot. Is the replica gone for good? Is the CDC connector just paused?
  2. If it's truly abandoned, drop it:
SELECT pg_drop_replication_slot('old_replica_slot');
  1. WAL is recycled at the next checkpoint (CHECKPOINT; to hurry it).
  2. If a consumer still exists, it will need to be re-initialised — a new base backup for a replica, a new snapshot for a logical consumer.

You can't drop an active slot; stop the consumer first.

Operational rules

  • Every slot has an owner and an alert. Inactive for more than N minutes → page someone.
  • Set max_slot_wal_keep_size on every primary that has slots.
  • Put WAL on a volume with headroom, and monitor it separately.
  • Tear down slots with their consumers — make slot cleanup part of decommissioning a replica or pipeline.
  • For tests, use temporary slots (they disappear when the session ends).

The summary

  • Slots retain WAL (and hold back xmin) until their consumer confirms progress.
  • An inactive slot can fill the disk and cause bloat.
  • Monitor pg_replication_slots, cap retention with max_slot_wal_keep_size, and drop abandoned slots.

EasySpawn runs Postgres next to your app with daily backups handled, and Claude Code can audit replication slots and WAL usage when you're setting up CDC or replicas. See how it works or join the waitlist.

Related: Postgres Read Replicas · Postgres Point-in-Time Recovery · Postgres Major Version Upgrade · Transactional Outbox Pattern

Keep reading