A Stale PostgreSQL Replication Slot Can Fill Your Disk
Last edited on August 3, 2026

Do not delete files from pg_wal to solve a growing PostgreSQL disk. A persistent physical or logical replication slot tells PostgreSQL that a consumer may still need old write-ahead log records. If that consumer stops advancing, the primary can retain WAL from the slot’s restart_lsn while ordinary database traffic keeps creating more.

Safe recovery starts by measuring the retained bytes and identifying the slot owner. Repair a consumer that must return, retire and drop only an approved obsolete slot, or re-seed a consumer whose required WAL is already gone. Until that decision is explicit, a quick cleanup can exchange a disk incident for broken replication or lost change-data-capture continuity.

One stalled consumer can become a disk incident

A replication slot is a durability promise, not a second copy of the database. PostgreSQL preserves resources that a named consumer still needs. For WAL retention, restart_lsn marks the oldest log sequence number that might be required. Logical slots also expose confirmed_flush_lsn, the point through which their consumer has acknowledged data.

When a replica, migration process, Debezium connector or other CDC reader goes offline, the slot can remain while its owner disappears. New transactions continue moving the current WAL position forward. The distance between current LSN and restart_lsn becomes retained history, and the corresponding files remain under PostgreSQL’s control.

max_wal_size does not turn that promise into a hard slot cap. PostgreSQL’s current pg_replication_slots documentation calls the over-limit but still retained state extended. A slot-specific cap is governed by max_slot_wal_keep_size; its default of -1 allows unlimited retention.

First prove that WAL is the space consumer and that slots are plausible owners. Run these read-only checks through an administrative PostgreSQL session:

SHOW server_version;
SHOW max_slot_wal_keep_size;
SELECT pg_size_pretty(sum(size)) AS pg_wal_size
FROM pg_ls_waldir();

Filesystem free space still matters because PostgreSQL shares the volume with relation files, temporary work, logs and package data. Check it from the operating system, but do not turn the inspection into a recursive cleanup:

df -hT

Unknown files are evidence, not disposable bytes. If the volume is already critically full, reduce avoidable write load and free only independently understood space outside pg_wal while the database owner classifies the slot.

Name every slot owner before touching it

Start with a portable inventory that works across commonly deployed supported PostgreSQL releases. The retained-byte expression is an estimate of WAL distance, not a filesystem quota, but it ranks the slots that deserve attention.

SELECT
  slot_name,
  slot_type,
  database,
  active,
  active_pid,
  restart_lsn,
  confirmed_flush_lsn,
  pg_size_pretty(
    pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)
  ) AS retained_wal
FROM pg_replication_slots
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)
         DESC NULLS LAST;

An inactive slot is not automatically stale. A standby may be inside an approved maintenance window, or a logical consumer may be restarting after an upgrade. Conversely, an active session does not prove healthy progress; a connected consumer can be stuck while the primary keeps producing WAL.

For each row, write down owner, consumer, business purpose, expected return time and rebuild method. Physical slots usually map to streaming standbys. Logical slots should map to a subscription, decoding client or CDC pipeline. A name that cannot be connected to an accountable owner belongs in an incident ledger, not an immediate drop command.

On releases that expose the enhanced fields, inspect state and remaining safety explicitly. Confirm that these columns exist on every supported major before deploying this query across a mixed-version fleet.

SELECT
  slot_name,
  active,
  wal_status,
  safe_wal_size,
  inactive_since,
  invalidation_reason
FROM pg_replication_slots
ORDER BY safe_wal_size NULLS FIRST;

safe_wal_size reports how many more bytes can be written before a finite slot limit risks the lost state. A null value can mean the slot is already lost or that no finite limit is configured, so null is not a green status. Read it together with wal_status, the configured limit and filesystem runway.

Watch bytes, age and validity together

One snapshot can find the largest holder; two or more samples reveal direction. Capture current LSN, retained bytes and free filesystem bytes on the same timeline. A returning consumer should move restart_lsn forward fast enough that retained WAL eventually falls despite ongoing writes.

Signal What it establishes Escalation boundary
Retained WAL by slot Which continuity promise holds the most history Growth continues across samples
active plus LSN movement Whether a connected consumer is actually progressing Active session, unchanged restart/flush LSN
wal_status / invalidation Whether required WAL remains usable unreserved, lost, or an invalidation reason
Filesystem free bytes How long the primary can continue safely Runway falls inside the response window

Translate bytes into time by measuring WAL generation during representative load. Sample the current LSN, wait through a defined observation interval, then calculate the difference. Avoid treating a quiet overnight rate as the production peak.

SELECT clock_timestamp() AS sampled_at,
       pg_current_wal_lsn() AS current_lsn;

The next sample’s LSN difference divided by elapsed seconds gives bytes per second. Runway is free bytes divided by net retained-byte growth, with a reserve kept for checkpoints and normal database work. Alerting only at 90% disk usage ignores whether the workload has six days or six minutes left.

Workload planning should therefore include VPS storage configuration that can sustain measured database writes and the agreed consumer recovery window. More disk buys time; it does not repair an ownerless slot.

The response branches on ownership

The correct action is determined by continuity, not by which command frees space fastest. Freeze unrelated replication changes while the owner decision is made, and record the exact slot name before any write operation.

The consumer is expected to return

Repair its network, credentials, process, subscription or replica state. Then sample both consumer progress and retained bytes. Recovery requires forward LSN movement, not merely an active PID or a connected dashboard.

If the consumer cannot catch up before disk runway expires, decide explicitly between adding temporary capacity, throttling avoidable writers, or re-seeding the consumer. Do not quietly drop its slot and hope the application recreates continuity correctly.

The consumer was permanently retired

Obtain approval from the recorded owner and preserve the last observed slot state. Dropping the named slot through PostgreSQL allows obsolete WAL to become recyclable at subsequent checkpoints:

SELECT pg_drop_replication_slot('retired_consumer_slot');

That function is intentionally shown with a placeholder. Dropping a slot is a continuity decision: a physical standby may need a new base backup, while a logical consumer may need a new baseline or reconciliation. If the slot is active, stop and resolve the active owner rather than forcing filesystem changes underneath PostgreSQL.

The slot is already lost or invalid

Once required WAL has been removed, the old position cannot be made valid by reconnecting. The wal_status and invalidation fields distinguish that state from a merely inactive consumer. Rebuild the physical standby or reinitialize the logical pipeline according to its own supported procedure.

Unknown owner: quarantine the decision. Preserve the row, search deployment manifests and service configuration, and ask the teams responsible for replicas, analytics and CDC. A few minutes of ownership work is cheaper than silently severing an audit or data pipeline.

PostgreSQL WAL retention map linking current WAL production, a replication slot restart LSN, consumer progress, disk runway, and repair, reseed or drop decisions.
A slot is a retention promise. The safe action depends on who owns that promise and whether the consumer can still continue from it.

FAQ

Can PostgreSQL delete WAL held by an inactive replication slot automatically?

PostgreSQL does not necessarily remove that WAL. With max_slot_wal_keep_size=-1, a persistent slot may retain unlimited WAL. A finite limit can allow required segments to be removed at a checkpoint, but the affected consumer may then need reinitialization.

Can I remove old files manually from pg_wal?

Manual removal is unsafe. PostgreSQL owns pg_wal and decides which segments are required for crash recovery, archiving and replication. Identify the retaining slot or other subsystem and change that owner through supported PostgreSQL controls.

Does max_wal_size cap WAL retained by replication slots?

max_wal_size is not a slot-retention cap. It guides ordinary checkpoint behavior, while slot-retained files can push WAL beyond it. PostgreSQL labels this retained-over-limit condition extended in pg_replication_slots.

When is it safe to drop a PostgreSQL replication slot?

Drop a slot only when its consumer is formally retired or when an approved rebuild/reseed plan accepts losing that continuity point. Record the owner, last state and recovery consequence before running pg_drop_replication_slot().

Do physical and logical slots create the same disk risk?

Both physical and logical slots can retain WAL from restart_lsn. Logical slots can additionally retain transaction or catalog cleanup horizons through xmin and catalog_xmin, so vacuum pressure may accompany WAL growth.

Will restarting PostgreSQL clear a stale replication slot?

A normal PostgreSQL restart preserves persistent replication slots. It may interrupt workload and consumer sessions without resolving the slot’s ownership or retained-WAL distance.

Put retention inside a capacity budget

Prevention begins with a business recovery window, not a copied gigabyte value. Estimate peak WAL generation rate, multiply it by the longest consumer outage you agree to tolerate, then add operational margin. Compare that number with the database volume’s usable headroom and checkpoint needs.

PostgreSQL 13 and later provide max_slot_wal_keep_size. The official replication settings reference warns that a finite limit protects disk at the cost of potentially making a lagging slot unusable. Treat the value as an explicit tradeoff:

# Example only: derive this value from measured WAL rate and outage policy.
max_slot_wal_keep_size = '64GB'

Apply configuration through the server’s controlled configuration path, reload only after validation, and confirm the effective value. A generic 64GB copied from an article is not a capacity plan.

Monitoring should combine per-slot retained bytes, active state, LSN movement, wal_status, safe_wal_size where available, pg_wal bytes and filesystem free space. Route every persistent slot to an owner and alert before runway crosses that owner’s realistic response time. Current PostgreSQL releases also expose more lifecycle detail than older majors, so version the query and dashboard with the database fleet.

Connection storms are a different pressure domain. Once WAL retention is controlled, bounded PostgreSQL connection intake provides a separate path for protecting backends from excessive sessions. More operational material lives in Voxfor’s database reliability library.

Close when producer, consumer and disk agree

Recovery is complete only when three observations agree: the primary continues serving normal writes, the intended consumer advances or has an approved new baseline, and filesystem runway stops shrinking. A smaller pg_wal directory by itself proves none of those outcomes.

Keep the incident record short and reusable: slot name and type, owner, first and final LSNs, retained-byte samples, WAL generation rate, lowest free-space point, action taken, continuity consequence and next alert threshold. That record turns a hidden retention promise into an operated capacity boundary—and prevents the next abandoned consumer from becoming another disk emergency.

Share this Post

Leave a Reply

Your email address will not be published. Required fields are marked *