PostgreSQL Replication Slots: Create, Monitor & Troubleshoot

A PostgreSQL replication slot is a mechanism that guarantees WAL (Write-Ahead Log) segments are retained on the primary server until a subscriber or replica has consumed them. Replication slots prevent the primary from recycling WAL files that a replica still needs, but an inactive or lagging slot can cause WAL to accumulate indefinitely and fill your disk.

The Problem

Replication slots solve a real problem: without them, a slow or temporarily disconnected replica can fall behind, and the primary may recycle the WAL segments the replica needs to catch up. When that happens, the replica must be re-initialized from scratch — a painful, time-consuming operation on large databases.

But replication slots create a different risk. A slot tells PostgreSQL "do not delete any WAL after this point until I say so." If the consumer of that slot disconnects and never reconnects — a failed replica that was decommissioned, a logical replication subscription that was dropped without cleaning up the slot, or a CDC tool that crashed — the slot holds WAL segments indefinitely. PostgreSQL will not recycle them regardless of how much disk space they consume.

This is the most common replication slot disaster: WAL accumulates for hours or days, pg_wal/ grows from a few gigabytes to tens or hundreds of gigabytes, and eventually the disk fills up. When that happens, PostgreSQL cannot write new WAL, all transactions stall, and the database effectively goes down. The irony is that the slot was created to protect replication — and it ended up taking down the primary.

The problem is compounded in environments with multiple replication slots. Each slot independently retains WAL from its own confirmed position. If you have 5 slots and one falls behind, that single lagging slot determines the WAL retention floor for the entire system. The other 4 slots may be current, but WAL cannot be recycled until the lagging slot advances.

Logical replication slots are particularly prone to this because they are often created by external tools (Debezium, pglogical, pg_recvlogical) that manage their own lifecycle. If the tool is uninstalled or misconfigured, the slot persists on the primary with no consumer to advance it.

How to Detect It

Check all replication slots and their current state:

-- List all replication slots with lag and activity status
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_size,
    wal_status
FROM pg_replication_slots
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC;

The critical columns are active (is anything consuming this slot?), retained_wal_size (how much WAL is this slot holding), and wal_status (should be reserved — if it shows lost, the slot has fallen too far behind and its WAL has been recycled despite the slot). Any slot where active = false and retained_wal_size is growing deserves immediate investigation.

Monitor the total WAL directory size and correlate it with slot lag:

-- Check total WAL size on disk (PostgreSQL 13+)
SELECT
    pg_size_pretty(
        sum(size)
    ) AS total_wal_size,
    count(*) AS wal_file_count
FROM pg_ls_waldir();

-- Check replication slot lag alongside wal_level and max_replication_slots
SELECT
    name,
    setting
FROM pg_settings
WHERE name IN ('wal_level', 'max_replication_slots', 'max_slot_wal_keep_size');

The max_slot_wal_keep_size parameter (PostgreSQL 13+) is your safety valve. When set, it limits how much WAL a single slot can retain. If a slot exceeds this limit, its wal_status changes to lost and PostgreSQL recycles the WAL. The replica will need to be re-initialized, but the primary stays alive. If this parameter is unset (the default), there is no limit — a single inactive slot can consume all available disk space.

Check for slots that have no corresponding active subscription or standby:

-- Find inactive slots with no consumer and significant WAL retention
SELECT
    s.slot_name,
    s.slot_type,
    s.database,
    s.active,
    pg_size_pretty(
        pg_wal_lsn_diff(pg_current_wal_lsn(), s.restart_lsn)
    ) AS retained_wal_size,
    sub.subname AS subscription_name
FROM pg_replication_slots s
LEFT JOIN pg_subscription sub
    ON sub.subslotname = s.slot_name
WHERE s.active = false
    AND pg_wal_lsn_diff(pg_current_wal_lsn(), s.restart_lsn) > 1024 * 1024 * 100  -- > 100 MB lag
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), s.restart_lsn) DESC;

A slot with no matching subscription and growing WAL retention is almost certainly orphaned and should be dropped.

How myDBA.dev Shows It

myDBA.dev's Replication page provides continuous monitoring of all replication slots, their lag, and WAL accumulation trends. Each slot is displayed with its type (physical or logical), active/inactive status, retained WAL size, and a time-series chart showing how the lag has changed over the past hours or days. An inactive slot with growing lag is immediately flagged as a risk.

Replication page showing slot status, WAL retention per slot, and lag trends

The WAL accumulation chart on the dashboard correlates disk usage in pg_wal/ with replication slot activity. When a slot falls behind, you can see the WAL directory growing in real time and identify exactly which slot is responsible. This is critical during incident response — when disk usage is climbing, you need to know whether to drop a slot or wait for a replica to catch up.

Dashboard showing WAL directory growth correlated with replication slot lag

The health check system includes replication slot checks that alert on inactive slots, slots with excessive lag, and missing max_slot_wal_keep_size configuration. These catch orphaned slots before they become a disk space emergency.

How to Fix It

Drop an orphaned or inactive slot that no longer has a consumer:

-- First, confirm the slot is truly orphaned (no active consumer, no matching subscription)
SELECT slot_name, active, active_pid, slot_type
FROM pg_replication_slots
WHERE slot_name = 'old_replica_slot';

-- Drop the slot (this immediately allows WAL recycling)
SELECT pg_drop_replication_slot('old_replica_slot');

For active slots that are lagging heavily, the fix depends on the slot type.

Physical replication slots (streaming replication to standby servers): Check the replica. If it is running but lagging, it may need more resources (CPU, I/O, network bandwidth) to apply WAL faster. If the replica is down, either bring it back or drop the slot:

-- Check the streaming replication state from the primary
SELECT
    client_addr,
    state,
    sent_lsn,
    write_lsn,
    flush_lsn,
    replay_lsn,
    pg_size_pretty(
        pg_wal_lsn_diff(sent_lsn, replay_lsn)
    ) AS replay_lag
FROM pg_stat_replication;

Logical replication slots (subscriptions, CDC tools): Check the subscription on the subscriber side. A disabled subscription will not consume WAL:

-- On the subscriber: check subscription status
SELECT subname, subenabled, subslotname
FROM pg_subscription;

-- Re-enable a disabled subscription
ALTER SUBSCRIPTION my_subscription ENABLE;

If the logical replication subscriber was permanently removed, drop the slot on the primary. Logical slots cannot be "reassigned" — if the consumer is gone, the slot must be dropped and a new subscription created if needed.

Set a safety limit to prevent future WAL accumulation disasters:

-- Limit WAL retained per slot to 10 GB (PostgreSQL 13+)
-- Requires a reload, not a restart
ALTER SYSTEM SET max_slot_wal_keep_size = '10GB';
SELECT pg_reload_conf();

Choose a value large enough that a replica experiencing temporary network issues can recover, but small enough that a permanently dead slot cannot fill your disk. A reasonable starting point is 2-5x your typical hourly WAL generation rate.

Create replication slots properly from the start:

-- Create a physical replication slot for a streaming replica
SELECT pg_create_physical_replication_slot('replica_east_1');

-- Create a logical replication slot for CDC or logical replication
SELECT pg_create_logical_replication_slot('cdc_debezium', 'pgoutput');

Always name slots descriptively — include the consumer name or purpose so it is obvious what each slot is for when you audit them later.

How to Prevent It

Always set max_slot_wal_keep_size. This is the single most important prevention measure. Without it, a single orphaned slot can take down your primary by filling the disk. A limit of 5-20 GB is appropriate for most workloads — enough headroom for temporary lag, but a hard ceiling that prevents catastrophe.

Monitor slot activity, not just existence. A slot that exists and is marked active is fine. A slot that exists but has been inactive for hours is a ticking time bomb. Set up alerts on slot inactivity duration and WAL retention size. myDBA.dev tracks both metrics continuously and will alert when a slot exceeds configurable lag thresholds.

Document every replication slot. Maintain a record of which slot belongs to which consumer (replica, CDC tool, subscription). When a consumer is decommissioned, the corresponding slot must be dropped as part of the decommission procedure — not as an afterthought. Orphaned slots almost always result from incomplete decommission checklists.

Tune max_replication_slots conservatively. The default is 10, which is more than most environments need. Keeping it close to your actual slot count makes it obvious when an unexpected slot appears. If you normally run 3 slots and suddenly have 5, that discrepancy is easier to notice when the max is 5 rather than 10.

Audit slots on a schedule. Run a weekly check that compares active replication slots against your documented consumers. Any slot without a known consumer should be investigated immediately. Automated health checks that flag orphaned or lagging slots turn this from a manual process into a continuous safeguard.

FAQ

How do I drop a replication slot in PostgreSQL?

Use the pg_drop_replication_slot() function: SELECT pg_drop_replication_slot('slot_name');. The slot must be inactive (no active consumer connected). If the slot is active, you need to stop the consumer first — either shut down the replica, disable the subscription (ALTER SUBSCRIPTION ... DISABLE), or terminate the consuming connection. Once dropped, WAL that was retained only for that slot becomes eligible for recycling immediately.

How do I create a replication slot?

For physical replication (streaming replicas): SELECT pg_create_physical_replication_slot('slot_name');. For logical replication: SELECT pg_create_logical_replication_slot('slot_name', 'pgoutput');. The second argument for logical slots specifies the output plugin — pgoutput is the built-in plugin used by native logical replication. Other plugins like wal2json or test_decoding are available for CDC tools.

What happens when a replication slot retains too much WAL?

The WAL directory (pg_wal/) grows continuously until either the slot's consumer catches up, the slot is dropped, or the disk fills up. If the disk fills, PostgreSQL cannot write new WAL, all transactions block, and the database effectively stops. On PostgreSQL 13+, setting max_slot_wal_keep_size prevents this by capping WAL retention per slot — once the limit is exceeded, the slot's wal_status changes to lost and WAL is recycled. The consumer will need to be re-initialized, but the primary survives.

What is the difference between physical and logical replication slots?

Physical replication slots retain WAL for streaming replication to standby servers. The standby receives the raw WAL stream and applies it byte-for-byte. Logical replication slots retain WAL for logical decoding — the WAL is decoded into logical change events (inserts, updates, deletes) that can be consumed by subscriptions, CDC tools, or custom applications. Logical slots are more flexible (they can replicate specific tables, transform data, replicate across major versions) but consume more CPU on the primary for the decoding process.