PostgreSQL Replication Slots Are Retention Leases

Sep 3

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;
  • xmin and catalog_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_since and invalidation_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:

PolicyProtectsSacrifices under long outage
unlimited retentionconsumer continuityprimary disk safety
finite slot limitprimary capacityconsumer may need rebuild
no slot plus archiveprimary working setrecovery 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 activity

Alert before invalidation using burn rate:

time_to_loss ~= safe_wal_size / recent_wal_bytes_per_second

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

References

>