A replication slot is often introduced as a convenience: PostgreSQL keeps what a replica or CDC consumer still needs. Operationally, the slot is a lease against finite storage and cleanup capacity.
The invariant is:
Every slot must have an owner, a recovery objective, and a bounded retention budget.
Physical slots protect WAL required by a standby. Logical slots can protect both WAL and catalog or row visibility horizons needed for decoding. A disconnected consumer stops making progress, but its claim on the primary can continue growing.
Read the slot as a contract #
pg_replication_slots exposes several distinct boundaries:
restart_lsn: oldest WAL the consumer may still require;confirmed_flush_lsn: logical consumer acknowledgement point;xminandcatalog_xmin: row/catalog horizons vacuum must preserve;wal_status: whether required WAL is reserved, extended, unreserved, or lost;safe_wal_size: remaining WAL bytes before the slot risks becoming lost;inactive_sinceandinvalidation_reason: lifecycle evidence.
Monitor retained bytes directly:
SELECT slot_name, slot_type, active, wal_status, safe_wal_size,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal,
inactive_since, invalidation_reason
FROM pg_replication_slots
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC NULLS LAST;Lag in seconds is insufficient. Ten minutes during a bulk load can retain more WAL than hours of quiet traffic. Capacity is driven by byte rate and recovery time.
Unlimited retention is delayed failure #
With max_slot_wal_keep_size = -1, slots may retain unlimited WAL. That protects consumers until pg_wal fills the volume and threatens the primary. A finite limit protects the database but allows a lagging slot to become unusable and require re-seeding.
That is the real trade-off:
| Policy | Protects | Sacrifices under long outage |
|---|---|---|
| unlimited retention | consumer continuity | primary disk safety |
| finite slot limit | primary capacity | consumer may need rebuild |
| no slot plus archive | primary working set | recovery depends on archive completeness |
Choose the consumer recovery objective first. If rebuilding a 20 TB downstream store takes three days, a 30-minute retention budget is not honest resilience.
Lifecycle must be automated carefully #
idle_replication_slot_timeout can invalidate inactive slots, with enforcement occurring at checkpoint. It is useful for abandoned consumers, not a substitute for ownership. Some slots—such as synchronized standby slots—have special lifecycle behavior.
Maintain a registry outside PostgreSQL:
slot -> owning service -> escalation -> retention bytes/time
-> rebuild procedure -> criticality -> expected activityAlert before invalidation using burn rate:
time_to_loss ~= safe_wal_size / recent_wal_bytes_per_secondUse a conservative high-percentile WAL rate, especially around migrations and backfills. Page rewrites and index work can invalidate yesterday’s forecast.
Dropping a slot is a data decision #
Deleting an unknown inactive slot may release disk immediately while permanently removing the consumer’s continuation point. Before dropping it, identify the owner, validate whether the consumer has another checkpoint, preserve required evidence, and decide whether a full snapshot is acceptable.
Likewise, restarting the consumer is not enough if wal_status = lost. The required history is already gone. Recovery must move to a new base snapshot or another authoritative source.
The CTO decision #
Treat every slot like allocated storage with an accountable customer. Budget retained WAL, test re-seeding, measure time to loss, and make slot invalidation an explicit product trade-off.
Replication slots make retention precise. They do not make it free.