A replication slot is filling your disk with WAL: find it, cap it, drop it

A logical replication slot that nobody reads keeps every WAL file since it stopped. By default there's no limit. We reproduced it, capped it, and watched the slot get invalidated.

The short answer

Run select slot_name, active, wal_status, inactive_since from pg_replication_slots;. A slot with active = f and an old inactive_since is holding WAL for a consumer that isn’t reading. If nothing needs it, drop it with select pg_drop_replication_slot('name'); and the next checkpoint frees the space. To stop it happening again, set max_slot_wal_keep_size: by default it’s -1, which lets slots retain an unlimited amount of WAL.2 On PostgreSQL 18 you can also set idle_replication_slot_timeout to invalidate slots that sit unused.2

Deedglass reads what Postgres actually committed, not what the agent reported. In our demo a hidden trigger turned 1 row into 301; a person countersigned the undo and all 301 rows came back.

Why a slot keeps WAL

A replication slot is a promise: “the consumer reading this slot hasn’t seen anything after this point yet, so keep it.” The slot’s restart_lsn is the oldest WAL the consumer might still need, and checkpoints won’t remove WAL after it, unless it falls further behind than max_slot_wal_keep_size.1

That promise is useful while the consumer runs: a replica, a change data capture tool, an audit pipeline. It becomes a disk leak when the consumer stops. A test someone ran once, a Debezium connector that was turned off, a tool that created a slot and crashed. Every write after that point stays on disk, and a busy database writes a lot of WAL. Tools that read logical decoding, including audit tools that watch what agents change, all use slots, so this is worth knowing before you install one.

Find the slot

Code A database command in SQL. The text around it says what it does.

select slot_name, slot_type, active, wal_status, inactive_since,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) as retained,
       safe_wal_size
from pg_replication_slots;

What the columns tell you:1

Column Meaning
active True if the slot is being streamed right now
inactive_since When it became inactive; NULL while streaming
wal_status = reserved Its WAL is within max_wal_size
wal_status = extended Past max_wal_size, but still retained
wal_status = unreserved No longer retains all it needs; some files go at the next checkpoint; can recover
wal_status = lost The slot is no longer usable
safe_wal_size Bytes of WAL that can be written before the slot is in danger of lost; NULL when the cap is -1
invalidation_reason Why it was invalidated: wal_removed, rows_removed, wal_level_insufficient or idle_timeout

retained is our addition: how far behind the slot is, in bytes of WAL.

A reproduction

We created a slot on PostgreSQL 18.6 and never read from it, then inserted 300,000 rows:

Code A database command in SQL. The text around it says what it does.

select pg_create_logical_replication_slot('forgotten_slot', 'pgoutput');
create table bulk(id int, pad text);
insert into bulk select g, repeat('x', 200) from generate_series(1, 300000) g;
checkpoint;

Code What the screen or file shows, copied as is.

   slot_name    | active | wal_status | retained | safe_wal_size
----------------+--------+------------+----------+---------------
 forgotten_slot | f      | reserved   | 76 MB    |

max_slot_wal_keep_size was -1, so safe_wal_size was empty: there was no limit to measure against. The pg_wal directory was 96 MB. On a production database, one night of writes behind a forgotten slot can be tens of gigabytes, and when pg_wal fills the disk the database stops accepting writes.

Cap it: max_slot_wal_keep_size

max_slot_wal_keep_size limits how much WAL slots may retain at checkpoint time. If a slot’s restart_lsn falls further behind than that, the consumer “may no longer be able to continue replication due to removal of required WAL files.”2 It’s a trade: you protect the disk and accept that a slow or stopped consumer may have to start again.

We set it to 32 MB on the same server:

Code A database command in SQL. The text around it says what it does.

alter system set max_slot_wal_keep_size = '32MB';
select pg_reload_conf();

Code What the screen or file shows, copied as is.

   slot_name    | wal_status | safe_wal_size
----------------+------------+---------------
 forgotten_slot | unreserved | -37 MB

The slot was already 37 MB past the cap. After another 300,000-row insert and a checkpoint:

Code What the screen or file shows, copied as is.

   slot_name    | active | wal_status | invalidation_reason
----------------+--------+------------+---------------------
 forgotten_slot | f      | lost       | wal_removed

The slot was invalidated, and the WAL it had been holding could be removed. Pick the cap from your disk, not from the example: big enough that a consumer restarting after a short outage still finds its WAL, small enough that pg_wal can’t fill the volume. The default, -1, means no limit at all.3

Expire idle slots (PostgreSQL 18)

PostgreSQL 18 adds idle_replication_slot_timeout. It invalidates slots that have been inactive for longer than the setting, measured from inactive_since; zero, the default, turns it off, and a value without a unit is seconds. Invalidation happens at a checkpoint, so it can lag by up to a checkpoint interval; you can force a checkpoint to act sooner. It doesn’t apply to slots that don’t reserve WAL, or to slots on a standby being synced from the primary.2

The two settings answer different questions. max_slot_wal_keep_size says “no slot may cost more than this much disk”. idle_replication_slot_timeout says “no slot may sit unused for longer than this”. A slot that’s active but slow is caught by the first; a slot that’s tiny but abandoned, by the second.

Drop it safely

Before dropping, find out who owns the slot. The name often says (debezium, airbyte, a tool’s own prefix). Then:

  1. Confirm nothing is reading it. active = f and an old inactive_since.
  2. Check whether the consumer can rebuild. Most CDC tools can take a fresh snapshot, but it may take hours or re-send every row downstream.
  3. Drop it: select pg_drop_replication_slot('forgotten_slot');
  4. Run a checkpoint (or wait for one), then check pg_wal shrank.

We did steps 3 and 4 on the test server, and pg_wal went from 96 MB to 64 MB.

If the disk is already full and Postgres won’t start, don’t delete files from pg_wal by hand. Free space elsewhere on the volume or grow it, start the server, then drop the slot.

A checklist

  • An alert on pg_replication_slots where active = f for more than an hour.
  • An alert on wal_status other than reserved.
  • max_slot_wal_keep_size set to a size your disk can afford.
  • On PostgreSQL 18, idle_replication_slot_timeout set for slots that should never sit idle.
  • Every slot has a known owner and a way to rebuild.
  • Any tool that creates a slot, including audit and CDC tools, documents how to remove it.

For background on what a slot is, see the replication slot glossary entry; for how audit tools use slots to watch what agents change, read pgaudit, triggers or logical decoding. To see which databases your agents can reach in the first place, paste the agent’s MCP config into the check page or run npx deedglass scan (how the grade works). To try the checks in your browser without installing anything, open the playground.

Written with AI assistance and checked against our claims register.

The takeaway

An unread slot held 76 MB of WAL after one bulk insert; with max_slot_wal_keep_size at 32MB it went unreserved, then lost, and the WAL was freed.

Read nextpgAudit, triggers or logical decoding: auditing what an AI agent wrote to Postgres Try it: install in 7 s

Related on this site

More posts

Sources

  1. 1PostgreSQL docs: pg_replication_slots · postgresql.org · checked 26 October 2026
  2. 2PostgreSQL 18 docs: Replication settings · postgresql.org · checked 26 October 2026
  3. 3PostgreSQL docs: Replication settings · postgresql.org · checked 18 October 2026

Published 26 October 2026, sources last checked 26 October 2026. If something here is out of date, write to hello@deedglass.com.

Try it on your own setup

npx deedglass scan

Grades only your own setup. Nothing is uploaded.

Pricing