Postgres Logical Replication: The Complete Guide

Intergalactic Data Labs
—
—
12 min read
Postgres logical replication decodes committed row changes from the write-ahead log and streams them, per table, to any client that speaks the pgoutput protocol. As of September 2026 there are three ways to consume it. Native CREATE SUBSCRIPTION copies into another Postgres, Debezium and PeerDB push it into Kafka or ClickHouse, and Filament, dlt, Airbyte, and Fivetran land it in typed tables. Snapshot consistency, slot hygiene, and the target decide which.
Way to consume | Targets | Initial snapshot | Deployment | License |
|---|---|---|---|---|
Native subscription | Postgres only | Per-table sync workers | None, built in | PostgreSQL |
Filament | Postgres, MySQL, warehouses, lakes | One exported snapshot | Binary, Go library, Helm | Apache 2.0 |
Debezium | Kafka topics, Debezium Server sinks | Consistent snapshot | Kafka Connect or Debezium Server | Apache 2.0 |
PeerDB | ClickHouse, Postgres | Full initial load | Multi-container Docker | AGPLv3 |
dlt | Any dlt destination | Optional snapshot | Python library | Apache 2.0 |
Airbyte | Airbyte catalog | Debezium snapshot | Hosted or self-hosted | ELv2 |
Fivetran | Fivetran catalog | Managed | Hosted only | Commercial, MAR |
What is Postgres logical replication?
Logical replication is the WAL, decoded into row changes and filtered by a publication. The Postgres manual defines it as "a method of replicating data objects and their changes, based upon their replication identity." Logical contrasts with physical replication, "which uses exact block addresses and byte-by-byte replication."
Four parts make it work. The write-ahead log records every change first. Logical decoding turns those records into "a coherent, easy to understand format," per the decoding docs, through the pgoutput plugin, "the standard one used for the built-in logical replication," per the protocol reference. A publication chooses which tables and operations to publish.
The fourth part is the replication slot, "a stream of changes that can be replayed to a client in the order they were made," per the decoding docs. Creating a slot over the replication protocol exports a snapshot of "exactly the state of the database after which all changes will be included in the change stream." That gives an external consumer a baseline and a stream that agree.
Physical replication ships the whole cluster instead, and WAL shipping "can only be done for the entire database server," per the comparison page.
Property | Physical (streaming) | Logical |
|---|---|---|
Unit | Whole cluster | Selected tables |
Format | Raw WAL bytes | Decoded row changes |
Target writable | No, read-only standby | Yes |
Cross major version | No | Yes |
Non-Postgres targets | No | Yes, through pgoutput |
DDL replicated | Yes | No |
What decides a logical replication setup?
Six criteria separate a setup that survives production from one that fills a disk on a Sunday.
Criterion | What to check | Source |
|---|---|---|
Snapshot consistency | Baseline and stream share one point | |
Slot and WAL retention | Slot advances, | |
Delete handling | Replica identity on every table | |
Schema changes | DDL is never replicated | |
Operational footprint | Processes and services you now run | Each tool's docs |
Destination | Postgres only, or anything |
Slot retention is the one that hurts. Slots "will prevent removal of required resources even when there is no connection using them," per the decoding docs, and "in extreme cases this could cause the database to shut down."
Deletes need a replica identity, the primary key by default, per the publication docs. Schema changes are the silent one, since "the database schema and DDL commands are not replicated," per the restrictions page.
How to set up Postgres logical replication step by step
Five steps take a stock server to a second Postgres receiving every change in commit order. This is the native path from the quick setup page, and every external consumer reuses the first two steps.
Step 1. Set wal_level to logical and restart
The WAL settings say this parameter "can only be set at server start." On RDS set rds.logical_replication to 1, per the RDS docs, and on Cloud SQL turn on cloudsql.logical_decoding, per the Cloud SQL docs.
Step 2. Create a replication role and allow it in pg_hba.conf
The pg_hba.conf line is the manual's own example, and the default of 10 for max_replication_slots covers a basic setup, per the replication settings.
Step 3. Copy the schema and create the publication
The schema "can be copied by hand using pg_dump --schema-only," per the restrictions page. A publication publishes insert, update, delete, and truncate by default, per CREATE PUBLICATION, and creating it "does not start replication."
Step 4. Create the subscription
This command "normally creates a replication slot on the publisher," per CREATE SUBSCRIPTION. Sync workers snapshot each table, then stream the changes made during the copy, per the architecture page.
Step 5. Verify the subscription and the slot
In pg_replication_slots, wal_status should read reserved, and lost "means that this slot is no longer usable." Insert, update, and delete a row on the publisher, then check the subscriber.
What are the ways to consume Postgres logical replication?
The native subscription is the reference implementation, and six external consumers speak the same pgoutput protocol to reach other targets.
Consumer | Real strength | Documented limit |
|---|---|---|
Native subscription | No software, transactional apply order | Same-named Postgres tables only, DDL and sequences by hand |
Filament | Snapshot and stream from one exported snapshot, CRC32-C per batch | Postgres source and sink are beta |
Debezium | Mature per-table Kafka topics with before and after images | Needs Kafka Connect or Debezium Server |
PeerDB | Fast Postgres to ClickHouse path, full Postgres types | Other destinations deprecated, seven containers, AGPLv3 |
dlt | Python-native, any dlt destination | Library, you own scheduling and state |
Airbyte | Large catalog, certified source | One destination per CDC source, raw plus final tables, ELv2 |
Fivetran | Managed, published DDL support table | Hosted only, MAR meter scales with rows |
The native subscription applies changes "in the same order as the publisher," per the manual, but only into same-named Postgres tables. Debezium's Postgres connector streams every table to its own Kafka topic and expects "Kafka and Kafka Connect are installed," or the Kafka-less Debezium Server.
PeerDB's README now names Postgres to ClickHouse and Postgres to Postgres as its only maintained paths. Its LICENSE file is AGPLv3, whatever the README badge says. dlt's pg_replication source is a Python library over the same plugin. Airbyte's troubleshooting page says its xmin fallback "cannot support row deletions," and Fivetran's pricing meters per million monthly active rows.
Where does Filament fit?
Filament is a Go replication engine that consumes pgoutput and answers the criteria that break setups most often. Galaxy builds Filament. Its Postgres source is beta today, per the connector page, the stage where a connector runs end to end.
On snapshot consistency, the source docs say a table with no checkpoint is bootstrapped so that "the baseline and the change stream share one consistent point." That is the exported-snapshot mechanism from the manual, used as designed.
On slot hygiene, Filament owns the slot name per pipeline route, acknowledges "only the previously durable cursor," and drops retired slots "when a successor commits." Each steady-state run is a bounded catch-up cycle, so "continuous CDC is a re-request loop, so it can run on a schedule."
On deletes, the merge write mode applies them in order to maintain "a current replica," per the replication modes page. On integrity, the engine computes a CRC32-C checksum before every write and "the sink recalculates that checksum," per the integrity page. On footprint, it runs as one binary, a Go module, or a Helm chart, per the installation page, under an Apache 2.0 LICENSE.
Speed claims stay scoped. The public cohort measured full loads only, where Filament's 115 s on 298 million rows led every tool and Debezium's figure is its initial snapshot, not streaming. No public run measures CDC throughput, so this guide makes no speed claim for it.
How to run Postgres logical replication with Filament
Four more steps reuse the first two steps above and replace the subscription with a Filament pipeline. Filament creates the publication itself, so skip the manual CREATE PUBLICATION unless an administrator provisions one.
Step 6. Point the CLI at a Postgres-backed server
CDC runs "require a datastore with durable replication-stream admission," and the source docs name the PostgreSQL datastore for it. The Helm chart sets it with persistence.postgresql.dsn, and the quickstart runs the same stack locally with just infra and just dev.
Step 7. Create the CDC source and the sink
Connector fields become flags with a source- prefix and underscores become hyphens, per the CLI guide, so replication is --source-replication. Run filament source discover production to list tables with their primary keys, since CDC "requires wal_level=logical and a primary key on every selected table."
Step 8. Create a merge pipeline and run the first cycle
The first run creates the slot with an exported snapshot and reads each table in full through it, under a publication named filament by default. Merge means "inserts and updates upsert, and deletes delete," per the Postgres sink docs.
Step 9. Schedule it and confirm the slot advances
A pipeline "has at most one schedule" with a cron and an overlap policy, per the pipelines API. A healthy run ends completed with a cursor.checkpoint_saved event, per the runs and recovery page, and confirmed_flush_lsn moves forward after each cycle.
What are common logical replication pitfalls?
Five failures cover most outages, and each has a documented fix that applies to every consumer.
An idle slot fills the disk because a stopped consumer never confirms, so drop unneeded slots and set
max_slot_wal_keep_size, per the replication settings.An update or delete fails on the publisher because the table has no replica identity, so add a primary key or run
ALTER TABLE t REPLICA IDENTITY FULL, per the ALTER TABLE docs.An update arrives without an unchanged TOAST column because DEFAULT identity omits it, which Filament treats as an error until the table is set to FULL, per its source docs.
Replication stops after a migration because DDL is not replicated, so apply additive changes to the subscriber first, per the restrictions page.
Newly published tables never arrive because adding one "will require an ALTER SUBSCRIPTION ... REFRESH PUBLICATION," per ALTER PUBLICATION, while Filament keeps its own publication current.
Which option fits which job?
Pick by the constraint that breaks first. The target system decides more than anything else.
Job | Pick | Why |
|---|---|---|
Postgres replica, warehouse, or lake with verified batches | Filament | Exported-snapshot bootstrap, typed sinks, merge deletes |
In-place major version upgrade or cutover | Native subscription | Built in, transactional apply |
Event stream for many downstream services | Debezium | Per-table Kafka topics |
Postgres to ClickHouse Cloud | PeerDB | Native ClickHouse integration |
Python pipeline with custom transforms | dlt | Library, any dlt destination |
No software to run, any source | Fivetran | Managed, MAR billed |
For most teams keeping a replica, a warehouse, or a lake current from Postgres, Filament is the pick. It uses the exported snapshot as the manual intends, owns its slots, applies deletes in order, and verifies each batch. The native subscription is the narrow case, a one-time upgrade or cutover between two Postgres servers where you will manage schema and slots by hand.
Related guides
What Is Change Data Capture (CDC)? The Complete Guide covers the pattern.
Full Load vs Incremental vs CDC picks a read mode per table.
Best CDC Tools in 2026 ranks the consumers above.
Postgres to Snowflake and Postgres to ClickHouse run this stream into a warehouse.
Database Replication, the complete guide is the pillar.
Frequently asked questions
What is logical replication in Postgres?
Logical replication is a Postgres feature that decodes committed inserts, updates, and deletes from the write-ahead log and streams them per table. A publication on the source names the tables, and a subscription or any client speaking the pgoutput protocol receives the changes in commit order.
What is the difference between logical and physical replication in Postgres?
Physical replication ships the WAL byte for byte, so the standby is an exact read-only copy of the whole cluster. Logical replication decodes the WAL into row changes, so you can pick tables, replicate across major versions, write to the subscriber, and feed non-Postgres systems.
How do I enable logical replication in PostgreSQL?
Set wal_level to logical and restart, because that parameter only applies at server start. Give a role the REPLICATION attribute and allow it in pg_hba.conf. Create a publication on the source, then create a subscription on the target or connect a CDC tool to the publication.
What is a replication slot and why does it fill the disk?
A replication slot is a server-side bookmark that records how far a consumer has read. Postgres keeps every WAL segment past that point until the consumer confirms it. If the consumer stops and the slot stays, WAL accumulates without limit by default, which is why the docs say to drop unused slots and why max_slot_wal_keep_size exists.
What does logical replication not replicate?
Schema changes and DDL, sequence values, large objects, views, materialized views, and foreign tables are not replicated. The published tables must already exist on the subscriber with the same fully qualified names. Truncates replicate but can fail when foreign keys point to tables outside the subscription.
Can Postgres logical replication capture deletes?
Yes. Deletes are written to the WAL like any other change, so every consumer of a publication that publishes delete sees them, as long as the table has a replica identity. Polling methods that read updated_at or the xmin column cannot see a hard delete, which is the main reason to prefer logical replication.
Which tool is best for consuming Postgres logical replication?
Filament fits most teams. It bootstraps the stream from one exported snapshot, runs each catch-up as a bounded cycle you can cron, verifies every batch with a CRC32-C checksum, and runs as one binary inside your network. Native CREATE SUBSCRIPTION covers a one-time cutover between two Postgres servers.
How do I set up Postgres CDC with Filament?
Set wal_level to logical, give a role the REPLICATION attribute, and point the Filament CLI at a server backed by its PostgreSQL datastore. Create a Postgres source with replication set to cdc, create a sink, and create a pipeline with merge as its write mode. The first run snapshots through the slot's exported snapshot and later runs replay the WAL.
More articles
Questions
Answered
FAQ
What does Galaxy do?
What is Filament?
What is enterprise context management?
What does working with Galaxy look like?
How do you handle security and compliance?
Why does Galaxy build in the open?
Company
Copyright © 2026 Galaxy. All rights reserved.