At Artie, we operate real-time CDC pipelines that depend on PostgreSQL replication slots to stream billions of change events per day into data warehouses. Replication slots are the mechanism that guarantees the primary server retains enough WAL (Write-Ahead Log) data for every consumer — whether that's a standby replica, a logical subscriber, or a CDC tool.
We've seen what happens when slots are misconfigured, abandoned, or misunderstood. This guide distills what we've learned into a practical reference: how to create and monitor PostgreSQL replication slots, how to drop an active replication slot safely, and the common pitfalls that catch teams off guard in production.
TL;DR — Replication slots guarantee WAL retention for downstream consumers, but an unmonitored slot can silently fill your disk. Create slots with intent, monitor their WAL retention, set max_slot_wal_keep_size as a safety net, and drop unused slots promptly.
What is a PostgreSQL replication slot?
A replication slot is a server-side object that tracks how far a replication consumer has read through the WAL stream. Without a replication slot, PostgreSQL may recycle WAL segments before a subscriber has consumed them, causing replication to break.
Replication slots solve this by guaranteeing WAL retention. The primary server will not discard WAL data that a slot's consumer still needs, regardless of wal_keep_size or checkpoint settings.
There are two types:
- Physical replication slots retain WAL for streaming replication to standby servers. The standby replays the exact byte-level changes.
- Logical replication slots decode the WAL into logical change events (inserts, updates, deletes) using an output plugin like
pgoutput. This is what tools like Debezium and Artie use for CDC.
Types of data replication in PostgreSQL
Before diving into operations, it's worth understanding where replication slots fit in the broader picture of replicating data with PostgreSQL.
Streaming replication (physical) sends raw WAL records to one or more standby servers. The standbys are exact copies of the primary. This is PostgreSQL's built-in high-availability mechanism and can use physical replication slots to guarantee WAL retention for standbys (though slots are optional — wal_keep_size or WAL archiving can also retain WAL).
Logical replication decodes WAL into row-level changes and publishes them through a publication/subscription model. Consumers can be other PostgreSQL instances, CDC pipelines, or external systems. This uses logical replication slots and requires tables to have a replica identity.
Snapshot-based replication (e.g., pg_dump or periodic COPY) captures the full state at a point in time. It doesn't use replication slots at all, but it's inefficient for large, frequently changing datasets.
For real-time, validated data replication to a data warehouse, logical replication slots combined with a CDC tool provide the best balance of reliability, efficiency, and low latency.
How to create a logical replication slot
Creating a logical replication slot requires a single SQL command:
SELECT * FROM pg_create_logical_replication_slot('artie', 'pgoutput');
This creates a slot named artie using the pgoutput output plugin (the standard plugin for PostgreSQL logical replication, available since PostgreSQL 10).
A few things to keep in mind:
- The slot is created on the primary server.
- The user running this command needs the
REPLICATIONprivilege or superuser access. - Once created, the slot immediately begins retaining WAL — even if nothing is consuming from it yet. This is important because an unconsumed slot will cause WAL to accumulate indefinitely.
Setting up a publication
Replication slots work hand-in-hand with publications, which define what data gets replicated. To view existing publications:
SELECT * FROM pg_publication;
To drop a publication that's no longer needed:
DROP PUBLICATION dbz_publication;
How to check the replica identity
Logical replication requires PostgreSQL to identify which row changed. This is controlled by the table's replica identity setting. If it's set to nothing, UPDATE and DELETE events won't include enough information for downstream consumers.
Check the replica identity for a specific table:
The possible values:
default— Uses the primary key. Works for most tables with a PK.nothing— No old row identity sent. Breaks UPDATE/DELETE replication.full— Sends the entire old row. Use for tables without a primary key.index— Uses a specific unique index. Use when the PK isn't suitable.
If a table lacks a primary key, set the replica identity to full:
ALTER TABLE your_table REPLICA IDENTITY FULL;
How to list all replication slots
To see every replication slot on the server:
SELECT * FROM pg_replication_slots;
This returns the slot name, plugin, slot type (physical or logical), whether it's active, the restart_lsn, and the confirmed_flush_lsn.
The two most important columns for operational health:
active: Is a consumer currently connected? An inactive slot still retains WAL.restart_lsn: The oldest WAL position the slot needs. The further behind this falls, the more disk space is consumed.
How to monitor replication slot size
WAL retention from replication slots is one of the most common causes of disk space issues in PostgreSQL. Monitoring slot size should be part of your operational baseline.
On the primary server
SELECT
slot_name,
wal_status,
pg_size_pretty(
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)
) AS retained_wal,
active,
restart_lsn
FROM pg_replication_slots;
On a standby (secondary) server
The only difference is the LSN function — use pg_last_wal_receive_lsn() instead of pg_current_wal_lsn():
SELECT
slot_name,
wal_status,
pg_size_pretty(
pg_wal_lsn_diff(pg_last_wal_receive_lsn(), restart_lsn)
) AS retained_wal,
active,
restart_lsn
FROM pg_replication_slots;
What the wal_status values mean
The wal_status column (available since PostgreSQL 13) indicates WAL availability for the slot:
reserved— WAL is withinmax_wal_size. Healthy.extended— WAL exceedsmax_wal_sizebut is retained by the slot orwal_keep_size.unreserved— WAL may be removed at the next checkpoint. Slot is at risk.lost— WAL has been removed. The slot is broken and must be recreated.
If you see unreserved or lost, the slot's consumer has fallen too far behind.
How to drop a PostgreSQL replication slot
To drop an inactive replication slot:
SELECT pg_drop_replication_slot('artie');
How to drop an active replication slot
If the slot is currently in use, pg_drop_replication_slot will fail. You need to terminate the consumer's backend process first:
Step 1: Find the PID of the process using the slot. The active_pid column in pg_replication_slots gives you this directly:
SELECT slot_name, active_pid
FROM pg_replication_slots
WHERE active = true;
Step 2: Terminate that backend.
SELECT pg_terminate_backend(<pid>);
Step 3: Drop the now-inactive slot.
SELECT pg_drop_replication_slot('artie');
Disabling a replication slot without dropping it
PostgreSQL does not provide a built-in way to disable or pause a replication slot. The pg_alter_replication_slot function (introduced in PostgreSQL 16) only modifies slot properties like failover and two_phase — it cannot deactivate a slot.
If you want to prevent WAL bloat from a misbehaving slot without losing the slot definition, the safest approach is:
- Terminate the consuming backend (
pg_terminate_backend). - Monitor the slot's
retained_walsize. - Drop and recreate the slot if WAL growth becomes critical.
WAL settings that affect replication slots
Several PostgreSQL configuration parameters interact with replication slot behavior:
wal_segment_size(default: 16MB, since 9.4) — Size of individual WAL segment files.max_slot_wal_keep_size(default: -1 / unlimited, since 13) — Maximum WAL retained per slot. Set this to prevent a single slot from filling your disk.wal_keep_size(default: 0, since 13) — Minimum WAL to retain regardless of slots. Replaceswal_keep_segments.max_wal_senders(default: 10, since 9.4) — Maximum number of concurrent WAL sender processes (one per active slot).
The most important one for production safety is max_slot_wal_keep_size. The default of -1 means a single inactive slot can consume all available disk space. Setting a reasonable limit (e.g., 100GB) will cause PostgreSQL to mark the slot as lost rather than filling the disk:
ALTER SYSTEM SET max_slot_wal_keep_size = '100GB';
SELECT pg_reload_conf();
Common pitfalls with PostgreSQL replication slots
Abandoned slots eat disk space. If a CDC tool, replica, or subscriber disconnects and never comes back, its slot continues retaining WAL indefinitely. On AWS RDS, this problem is compounded by unbounded WAL growth even on idle databases, where internal RDS heartbeats generate WAL that logical replication consumers never see. Monitor for inactive slots and set max_slot_wal_keep_size as a safety net.
Replica identity misconfiguration. If a table's replica identity is nothing, PostgreSQL will raise an error when an UPDATE or DELETE occurs on that table through a publication — it does not silently skip events. Verify replica identity before enabling replication on a table to avoid hard failures in your pipeline.
WAL sender process limits. Each active replication slot consumes a WAL sender process. The max_wal_senders setting (default: 10) limits how many can run concurrently.
Slot names are global. Slot names must be unique across the entire PostgreSQL instance, not just within a database. Plan your naming convention accordingly.
Frequently asked questions
What is a PostgreSQL replication slot?
A replication slot is a server-side object that tracks a consumer's position in the WAL stream. It guarantees that PostgreSQL retains all WAL segments the consumer still needs, preventing data loss during replication.
How do I drop an active replication slot in PostgreSQL?
First, find the PID of the backend using the slot via the active_pid column in pg_replication_slots. Terminate it with pg_terminate_backend(pid). Then drop the slot with pg_drop_replication_slot('slot_name'). You cannot drop a slot while a consumer is connected.
What is the difference between physical and logical replication slots?
Physical replication slots retain raw WAL for streaming replication to standby servers (byte-level copies). Logical replication slots decode the WAL into row-level change events using an output plugin, enabling CDC and cross-system data replication.
Can a replication slot fill my disk?
Yes. An inactive or slow-consuming slot will cause PostgreSQL to retain WAL indefinitely. Set max_slot_wal_keep_size to cap retention and prevent a single slot from exhausting disk space.
How do I check the size of a replication slot?
Query pg_replication_slots and use pg_wal_lsn_diff() to calculate the difference between the current WAL position and the slot's restart_lsn. The monitoring queries in this guide show the exact SQL for both primary and standby servers.
Putting it all together
PostgreSQL replication slots are essential for reliable data replication, but they require active monitoring. Here's a quick operational checklist:
- Create slots with a clear naming convention tied to their consumer.
- Check replica identity on every table before enabling logical replication.
- Monitor WAL retention per slot — alert when
retained_walexceeds a threshold. - Set
max_slot_wal_keep_sizeto prevent runaway disk usage. - Drop unused slots promptly when decommissioning consumers.
- Audit slot activity regularly — inactive slots are a ticking time bomb.
At Artie, we handle all of this automatically. Our platform manages replication slot lifecycle, monitors WAL retention, handles schema evolution, and delivers data to your warehouse in real time — so your team doesn't have to build and maintain this operational surface area.
If you're running CDC on PostgreSQL and want to stop worrying about replication slots, book a demo or request access to try Artie.


