A PostgreSQL replication slot records retention requirements for a consumer. Once the slot has reserved a WAL position, losing the consumer’s connection does not automatically release that position. The primary may keep generating log while the slot’s requirement remains unchanged. The operational risk is retained storage and an increasingly expensive catch-up or rebuild decision. Slot usability depends on retained WAL availability and the configured invalidation rules.
When it breaks
Not every newly created physical slot immediately holds WAL. Without immediate reservation, restart_lsn can remain null until the slot is first used. An inactive flag alone therefore does not quantify retention. Inspect the slot type, reserved position, availability status, and consumer ownership together. For logical slots, confirmed_flush_lsn describes acknowledged consumer progress; it is not a replacement for understanding the older restart_lsn retention requirement.
Dropping a slot removes that slot’s claim, not necessarily the files at that instant. Checkpoint cleanup and other retention requirements still matter. Other slots, archive backlog, or a WAL reserve can keep files present, and recycling may rename them without returning all allocated bytes to the filesystem. Adding every slot’s lag also overcounts overlapping WAL ranges shared by several consumers.
Retention limits exchange one risk for another. max_slot_wal_keep_size is enforced around checkpoints, not as an instantaneous hard disk quota. A sufficiently lagging slot can lose required WAL and become unusable. That protects against indefinite retention at the cost of abandoning that consumer’s existing catch-up path. Age alone is not authority to discard a live replication dependency.
Check it quickly
Run this script as a non-root account with PostgreSQL 18 server and client binaries in a Linux shell environment. It was executed on PostgreSQL 18.6. The private temporary cluster has no TCP listener and contains only test slots. Small logged inserts and forced segment switches make retention visible quickly; this is not a benchmark of business WAL volume.
#!/bin/sh
set -eu
umask 077
lab=$(mktemp -d /tmp/xbcatm-slots.XXXXXX)
mkdir "$lab/socket"
cluster=$lab/data
cleanup() {
if test -f "$cluster/postmaster.pid"; then
pg_ctl -D "$cluster" -m fast -w stop >/dev/null
fi
}
trap cleanup EXIT
initdb -D "$cluster" --no-locale --encoding=UTF8 --auth-local=trust --auth-host=reject >/dev/null
cat >> "$cluster/postgresql.conf" <<CONF
listen_addresses = ''
unix_socket_directories = '$lab/socket'
port = 56432
wal_level = replica
max_replication_slots = 4
max_slot_wal_keep_size = -1
min_wal_size = '32MB'
max_wal_size = '64MB'
checkpoint_timeout = '1h'
CONF
pg_ctl -D "$cluster" -l "$lab/server.log" -w -t 15 start >/dev/null
export PGHOST=$lab/socket PGPORT=56432 PGDATABASE=postgres PGUSER=$(id -un)
export PGCONNECT_TIMEOUT=2
psql -X -v ON_ERROR_STOP=1 -qAt <<'SQL'
SELECT * FROM pg_create_physical_replication_slot('unused_slot');
SELECT active,restart_lsn IS NULL FROM pg_replication_slots WHERE slot_name='unused_slot';
DO $$ BEGIN
IF (SELECT restart_lsn IS NULL FROM pg_replication_slots WHERE slot_name='unused_slot') IS NOT TRUE THEN
RAISE EXCEPTION 'unexpected immediate reservation';
END IF;
END $$;
SELECT pg_drop_replication_slot('unused_slot');
CREATE TABLE churn (value text);
SELECT * FROM pg_create_physical_replication_slot('held_slot',true);
SQL
segment=$(psql -X -v ON_ERROR_STOP=1 -qAt -c "SELECT pg_walfile_name(restart_lsn) FROM pg_replication_slots WHERE slot_name='held_slot'")
psql -X -v ON_ERROR_STOP=1 -qAt <<'SQL'
DO $$ BEGIN
FOR n IN 1..8 LOOP
INSERT INTO churn SELECT md5(g::text) FROM generate_series(1,5000) g;
PERFORM pg_switch_wal();
END LOOP;
END $$;
CHECKPOINT;
SELECT active,wal_status,pg_wal_lsn_diff(pg_current_wal_lsn(),restart_lsn)>64*1024*1024
FROM pg_replication_slots WHERE slot_name='held_slot';
DO $$ BEGIN
IF (SELECT NOT active AND wal_status='extended'
AND pg_wal_lsn_diff(pg_current_wal_lsn(),restart_lsn)>64*1024*1024
FROM pg_replication_slots WHERE slot_name='held_slot') IS NOT TRUE THEN
RAISE EXCEPTION 'inactive slot did not retain the expected WAL range';
END IF;
END $$;
SQL
test -f "$cluster/pg_wal/$segment"
echo inactive_slot_retains_old_segment
psql -X -v ON_ERROR_STOP=1 -qAt -c "SELECT pg_drop_replication_slot('held_slot')" >/dev/null
test -f "$cluster/pg_wal/$segment"
echo drop_did_not_immediately_remove_segment
psql -X -v ON_ERROR_STOP=1 -qAt -c CHECKPOINT
test ! -f "$cluster/pg_wal/$segment"
echo checkpoint_released_old_segment_name
psql -X -v ON_ERROR_STOP=1 -qAt <<'SQL'
ALTER SYSTEM SET max_slot_wal_keep_size='32MB';
SELECT pg_reload_conf();
SQL
attempt=0
while test "$(psql -X -v ON_ERROR_STOP=1 -qAt -c "SELECT current_setting('max_slot_wal_keep_size')='32MB'")" != t; do
attempt=$((attempt+1))
test "$attempt" -lt 100 || exit 1
sleep 0.1
done
psql -X -v ON_ERROR_STOP=1 -qAt <<'SQL'
SELECT * FROM pg_create_physical_replication_slot('limited_slot',true);
DO $$ BEGIN
FOR n IN 1..8 LOOP
INSERT INTO churn SELECT md5(g::text) FROM generate_series(1,5000) g;
PERFORM pg_switch_wal();
END LOOP;
END $$;
CHECKPOINT;
SELECT wal_status,invalidation_reason FROM pg_replication_slots WHERE slot_name='limited_slot';
DO $$ BEGIN
IF (SELECT wal_status='lost' AND invalidation_reason='wal_removed'
FROM pg_replication_slots WHERE slot_name='limited_slot') IS NOT TRUE THEN
RAISE EXCEPTION 'slot limit did not invalidate the test slot';
END IF;
END $$;
SELECT pg_drop_replication_slot('limited_slot');
SQL
printf 'lab_directory %s\n' "$lab"
The first physical slot is created without immediate reservation. Its f|t output means inactive with a null restart position. The script asserts that condition and removes this unused fixture. The next slot explicitly reserves WAL and never connects a consumer. After writes, segment switches, and a checkpoint, the observed state is f|extended|t: inactive, retaining beyond the configured maximum WAL target, and more than sixty-four mebibytes behind by LSN distance.
The script checks the original required segment still exists, drops that one test slot, and checks the file remains immediately afterward. A subsequent checkpoint removes the old segment name. Those observations distinguish releasing a claim from immediate filesystem cleanup. They do not claim the complete WAL directory becomes empty or that recycled storage necessarily disappears.
The final phase sets a thirty-two-mebibyte slot retention limit, confirms the reload took effect, and reserves a new slot. More logged work and segment switches push it beyond the limit. After checkpointing, the required result is lost|wal_removed; an assertion checks both availability state and invalidation reason. This is an observed loss of the slot’s recovery path, not successful catch-up by a replica.
Only fixture slots are dropped. The temporary server stops on exit, and the printed directory retains its logs and data for review. No real consumer is disconnected or rebuilt.
Decide from ownership and recoverability
Monitor restart_lsn, wal_status, safe_wal_size, inactivity, actual retained bytes, and free space. A null safety margin can mean unlimited retention or an already lost slot, so interpret it with the other fields. PostgreSQL 18 also exposes an invalidation reason, which distinguishes removed WAL from other causes instead of guessing from elapsed silence.
Before retiring a slot, identify its owner and whether that consumer is expected to resume. Decide explicitly when rebuilding is preferable to retaining more history. If a required segment is already gone, increasing the limit does not recreate it. Verify the replacement consumer’s consistent starting point and ongoing progress before considering the incident resolved. A smaller disk graph alone does not prove replication service has recovered.
Sources
- PostgreSQL 18 replication slots view, reserved positions, availability states, safety margins, and invalidation reasons, checked 2026-09-12.
- PostgreSQL 18 replication configuration, checkpoint-enforced slot retention limits, checked 2026-09-12.
- PostgreSQL 18 administrative functions, physical slot creation, immediate reservation, and dropping slots, checked 2026-09-10.