← Blog

pglogical vs Native Logical Replication in 2026: When to Migrate

A fair assessment of two systems that share a bloodline — one the pioneer, the other the heir. If you'll permit me, I have opinions about both.

The Butler of Gold Lapel · March 21, 2026 · 14 min read
Two silver trays, identical in form, one bearing the family crest. The other, a more recent acquisition, growing more polished with each passing season.

Two systems, one lineage — and a question of succession

Good evening. I find myself in the position of adjudicating a family matter, which is always delicate work.

pglogical and PostgreSQL's native logical replication share more than a passing resemblance. They share an author. Petr Jelinek and other engineers at 2ndQuadrant built pglogical as a standalone extension, proved the architecture worked in production, and then contributed the foundational logical replication framework that shipped in PostgreSQL 10. The native system is, in a meaningful sense, pglogical's descendant. The heir to the estate.

That lineage matters because it explains the current landscape. pglogical had row filtering, column filtering, and conflict resolution years before native logical replication existed. Native has been catching up ever since — steadily, methodically, one release at a time. By 2026, the gap between the two has narrowed to the point where the right choice is no longer obvious. I have seen teams agonize over this decision for weeks. I have also seen teams make it in minutes, incorrectly, and live with the consequences for years.

Allow me to spare you both fates. This is not a blanket recommendation — I am constitutionally incapable of those. It is a decision framework, built from having watched this particular succession play out across a great many households.

A brief history of the gap — or, how the heir learned the trade

pglogical arrived in 2015, targeting PostgreSQL 9.4. At the time, PostgreSQL had only physical (streaming) replication — byte-for-byte copying of the entire cluster. There was no way to replicate a subset of tables, filter rows, or replicate between different major versions. pglogical filled every one of those gaps. It was, if I may say so, a remarkable piece of work — the kind of extension that makes you grateful the PostgreSQL ecosystem rewards ambition.

PostgreSQL 10 (2017) introduced native logical replication with the basic publish/subscribe model. It was functional but limited: no row filtering, no column filtering, no conflict resolution, no TRUNCATE replication. For anything beyond simple table-level replication, pglogical remained necessary. The heir had entered the household, but was not yet ready to run it.

Then native started closing the gap, with the quiet determination of junior staff who intend to be senior:

  • PostgreSQL 11 (2018) — added TRUNCATE replication
  • PostgreSQL 14 (2021) — added binary transfer mode for streaming changes, reducing bandwidth
  • PostgreSQL 15 (2022) — added row filtering and column filtering in publications. This was the single biggest feature gap closure.
  • PostgreSQL 16 (2023) — added binary mode for initial data copy, parallel initial table sync improvements
  • PostgreSQL 17 (2024) — added conflict detection and logging, failover slot synchronization, replication slot preservation during pg_upgrade, and hash index support on subscribers

Each release brought native closer to feature parity. Closer. Not complete. And the remaining differences are precisely the ones that matter for certain architectures — which is, of course, why you are here this evening rather than simply running DROP EXTENSION pglogical and getting on with your life.

If I may draw your attention to the setup

The most immediate difference is operational complexity — which is to say, how much of your evening this will consume. Consider replicating a filtered subset of the orders table — only completed orders, only specific columns.

With pglogical, you install the extension, create nodes on both sides, and configure replication sets with filters:

pglogical — provider setup
-- pglogical: provider setup
CREATE EXTENSION pglogical;

SELECT pglogical.create_node(
  node_name := 'provider',
  dsn := 'host=primary port=5432 dbname=app'
);

-- Add tables with row filter and column filter
SELECT pglogical.replication_set_add_table(
  set_name := 'default',
  relation := 'public.orders',
  row_filter := $$ status = 'completed' $$,
  columns := '{id, customer_id, total, status}'
);

With native logical replication on PostgreSQL 15+, the same result requires no extension — just DDL:

Native — PG 15+ setup
-- Native logical replication: publisher setup (PG 15+)
CREATE PUBLICATION orders_pub
  FOR TABLE orders (id, customer_id, total, status)
  WHERE (status = 'completed');

-- On the subscriber
CREATE SUBSCRIPTION orders_sub
  CONNECTION 'host=primary port=5432 dbname=app'
  PUBLICATION orders_pub;

The native syntax is more concise. The publication definition is pure DDL — a single statement that declares what to replicate and how to filter it. No extension to install, no shared_preload_libraries change, no PostgreSQL restart required for the publication itself (though wal_level = logical still requires a restart if not already set). It is, I confess, rather elegant.

For straightforward publisher-subscriber setups, native is meaningfully simpler to configure and operate. Fewer moving parts, fewer things to go wrong during an upgrade, fewer dependencies to track. A well-run household does not employ specialized staff for duties the existing manor can perform natively. That said — and I shall elaborate shortly — there are duties where the specialist still earns their keep.

Where pglogical still commands the room

I should be dishonest if I suggested pglogical's advantages have entirely evaporated. They have not. Three areas remain where it offers capabilities that native logical replication does not match as of PostgreSQL 17, and they are not trivial ones.

Conflict resolution

This is the widest remaining gap. pglogical provides five built-in conflict resolution strategies that handle conflicts automatically:

pglogical — conflict resolution
-- pglogical: built-in conflict resolution
SET pglogical.conflict_resolution = 'last_update_wins';

-- Five strategies available:
--   error            — stop replication, require manual fix
--   apply_remote     — remote (provider) wins
--   keep_local       — local (subscriber) wins
--   last_update_wins — most recent commit timestamp wins
--   first_update_wins — earliest commit timestamp wins

-- Monitor conflicts
SELECT * FROM pglogical.show_subscription_status();

Native logical replication in PostgreSQL 17 detects and logs conflicts, but does not resolve them:

Native (PG 17) — conflict detection only
-- Native (PG 17): conflict detection is logged, but resolution
-- is limited. The subscriber logs the conflict:
--
-- LOG: conflict detected on relation "public.orders":
--   conflict=insert_exists
--   Key (id)=(42)
--   existing local tuple (42, 100, 'shipped')
--   remote tuple (42, 150, 'completed')
--
-- Default behavior: replication stops. You fix it manually.
-- No built-in last_update_wins or keep_local strategies.

For unidirectional replication — one publisher, one or more subscribers with no local writes — this difference is irrelevant. Conflicts do not arise when the subscriber is read-only. But for multi-directional setups, or scenarios where subscribers accept local writes that could conflict with replicated data, pglogical's automatic resolution is not merely convenient. It is the difference between a system that self-heals and one that pages you at 3 AM. I have a firm opinion on which of those I prefer, and I suspect you share it.

PostgreSQL 18 is expected to introduce formal conflict resolution to native logical replication. When it does, this gap will narrow significantly. But PostgreSQL 18 is not here yet, and "expected" is not "shipped." I have attended too many incidents caused by planning around features that had not yet been released to treat roadmap items as present-tense capabilities.

Cross-version reach

pglogical supports PostgreSQL 9.4 through 17. Native logical replication starts at PostgreSQL 10. If you are migrating off PostgreSQL 9.x — and yes, there are still production systems running 9.5 and 9.6 in 2026, I encounter them more often than I should like — pglogical is your only logical replication option. Native cannot help you until you reach version 10.

For the zero-downtime major version upgrade pattern, pglogical's wider version range means it can handle jumps that native cannot:

Zero-downtime upgrade: PG 14 to PG 17
-- Zero-downtime major version upgrade using pglogical
-- Step 1: Install pglogical on both PG 14 (old) and PG 17 (new)
-- Step 2: Configure provider on PG 14
SELECT pglogical.create_node(
  node_name := 'old_primary',
  dsn := 'host=pg14-server port=5432 dbname=app'
);
SELECT pglogical.replication_set_add_all_tables(
  'default', ARRAY['public']
);

-- Step 3: Configure subscriber on PG 17
SELECT pglogical.create_node(
  node_name := 'new_primary',
  dsn := 'host=pg17-server port=5432 dbname=app'
);
SELECT pglogical.create_subscription(
  subscription_name := 'upgrade_sub',
  provider_dsn := 'host=pg14-server port=5432 dbname=app',
  synchronize_data := true
);

-- Step 4: Wait for sync, verify lag is zero
-- Step 5: Point application traffic to PG 17
-- Step 6: Drop the subscription. Done.

Native logical replication can perform cross-version upgrades too, but only between versions 10 and above. And PostgreSQL 17 improved this story considerably: pg_upgrade now preserves logical replication slots on publishers and subscription state on subscribers, so upgrading a cluster already using native logical replication no longer requires tearing down and rebuilding subscriptions. Credit where it is due — that is a genuine improvement, and one that was long overdue.

DDL replication (limited, and I do mean limited)

pglogical offers limited DDL replication through event triggers and hooks — it can propagate certain schema changes from provider to subscriber. The keyword here is "limited." It handles simple cases (adding a column, creating an index) but not complex schema migrations. Native logical replication does not replicate DDL at all. Neither, I should note, apologizes for this.

Neither system provides full DDL replication. I mention pglogical's partial capability here out of fairness, not enthusiasm. If full DDL replication is a hard requirement, look at EDB Postgres Distributed (the commercial descendant of pglogical) or manage schema changes through your deployment tooling. The latter, in my experience, is where most well-run households end up regardless.

Where the heir has surpassed the pioneer

I would be doing you a disservice — and pglogical an undeserved favour — if I did not acknowledge that native logical replication has not merely caught up in several areas. It has pulled ahead. Progress deserves recognition, particularly when it is hard-won.

Parallel initial synchronization

When a new subscription is created, the subscriber needs to copy the current state of all published tables before it can start streaming live changes. pglogical does this sequentially — one table at a time, like a moving crew carrying furniture through a single doorway. Native logical replication synchronizes tables in parallel, using multiple workers. On databases with many large tables, the difference in initial sync time can be substantial — hours versus tens of minutes. I have witnessed this difference firsthand, and "substantial" is the polite word for it.

Binary transfer mode

PostgreSQL 14 introduced binary mode for streaming changes, and PostgreSQL 16 extended it to the initial data copy. Binary mode transmits data in PostgreSQL's internal format rather than converting to text and back, reducing both bandwidth and CPU usage. pglogical does not support binary transfer.

Failover slot synchronization (PG 17)

In high-availability setups with a physical standby, PostgreSQL 17 can automatically synchronize logical replication slots to the standby via the sync_replication_slots setting. If the primary fails and the standby is promoted, logical subscribers can reconnect without data loss or resynchronization. pglogical has no equivalent — a failover event requires manual reconfiguration of the replication topology. At three in the morning. While the incident channel fills with questions you cannot yet answer. I trust the distinction is clear.

Replication slot preservation during pg_upgrade (PG 17)

Prior to PostgreSQL 17, running pg_upgrade required dropping all logical replication slots, which meant subscribers had to resynchronize after the upgrade. PostgreSQL 17 preserves logical replication slots and subscription state through pg_upgrade. This makes version upgrades for native logical replication deployments significantly less disruptive.

pg_createsubscriber (PG 17)

The new pg_createsubscriber command-line tool converts a physical replica into a logical subscriber. This is useful for transitioning from physical to logical replication without a full data resynchronization — a task that previously required manual slot management and careful LSN coordination. The sort of task, if I may be candid, that separates a smooth transition from one that leaves scars in the runbook.

The full inventory, if you'll indulge me

I should like to lay the facts out plainly. The following table compares pglogical against native logical replication on PostgreSQL 17, which is the current production release as of early 2026. I have endeavoured to be scrupulously fair to both parties.

FeaturepglogicalNative (PG 17)Notes
Row filteringAll versionsPG 15+Syntax differs; functionally equivalent
Column filteringAll versionsPG 15+Native uses publication-level column lists
Conflict resolution5 built-in strategiesLog-only (PG 17)Native detects but does not auto-resolve
Cross-version replicationPG 9.4 to 17PG 10+Both support cross-version; pglogical reaches further back
Initial data syncSequentialParallelNative syncs tables concurrently — faster on large datasets
DDL replicationLimited (via hooks)NoNeither offers full DDL replication
TRUNCATE replicationYesPG 11+Native added TRUNCATE in PG 11
Binary transfer modeNoPG 14+ (PG 16 for initial copy)Binary mode reduces bandwidth, speeds up sync
Replication slot preservation on upgradeManualAutomatic (PG 17+)pg_upgrade now preserves logical replication slots
Failover slot syncNoPG 17+ (sync_replication_slots)Native can sync slots to standbys for HA
Hash index support on subscriberNoPG 17+Native can use hash indexes for applying changes
Requires extension installYesNo (built-in)Native ships with PostgreSQL
Active developmentMaintenance modeActive (core team)New features land in native each release

The pattern, I trust, speaks for itself. Native logical replication has closed most of the feature gap that justified pglogical's existence. The remaining advantages — conflict resolution, sub-PG-10 version support, and limited DDL replication — are significant for specific use cases but irrelevant for many common deployments. The question is not which system is "better" in the abstract. That question is beneath us. The question is which one serves your particular household.

In defence of keeping pglogical — three cases where loyalty is warranted

I do not counsel change for the sake of change. That is renovation, not improvement. pglogical remains the right choice in three concrete scenarios, and I would think less of myself if I failed to say so clearly.

You need automatic conflict resolution. If your architecture involves multi-directional replication, or subscribers that accept local writes, pglogical's five conflict resolution strategies (particularly last_update_wins) handle situations that would stop native replication cold. Until PostgreSQL 18 ships formal conflict resolution, this is pglogical's strongest differentiator. One does not dismiss a member of staff who possesses a skill no one else in the household can match.

You are replicating from PostgreSQL 9.x. Native logical replication does not exist before PostgreSQL 10. If your source database is on 9.4, 9.5, or 9.6, pglogical is your only option for logical replication. I shall refrain from commenting on why you are still running 9.x in 2026, though I confess the restraint costs me something.

It is already running and stable. If you have a well-functioning pglogical deployment and none of native's newer features (failover slot sync, parallel initial copy, binary mode) would meaningfully improve your operations, there is no urgency to migrate. pglogical continues to receive maintenance updates and remains compatible with PostgreSQL 17. A household that runs smoothly is not improved by rearranging the furniture to follow current fashion.

When the time has come to move on — the case for native

Native logical replication is the better default for new deployments and a worthwhile migration target for existing ones. I say this with no disrespect to pglogical — quite the opposite. The pioneer built the road. It is no insult to then drive on it.

You are starting fresh on PostgreSQL 15+. Native gives you row filtering, column filtering, parallel initial sync, and binary transfer mode without installing any extension. Fewer dependencies, simpler operations, and the full backing of the PostgreSQL core team's development cycle. When the manor itself can perform the duty, hiring external staff for it is an indulgence.

Your replication is unidirectional. One publisher, one or more read-only subscribers. No local writes on the subscriber. No conflict resolution needed. This describes the majority of logical replication deployments — far more than most teams realize — and native handles it with less operational overhead.

You want failover resilience. PostgreSQL 17's failover slot synchronization is a meaningful safety improvement that pglogical cannot match. If high availability is a priority — and I should be rather alarmed on your behalf if it were not — native on PG 17 provides better tooling.

Your managed service does not offer pglogical. Supabase, Neon, and some other managed PostgreSQL providers do not include pglogical. Native logical replication is your only option there, and on PG 15+ it covers most use cases well. One works with the staff available, not the staff one wishes were available.

My recommendations, plainly stated

ScenarioRecommendationReasoning
New deployment on PG 16+NativeRow/column filtering available, no extension dependency, parallel sync, active development
Cross-version upgrade (e.g., PG 12 to PG 17)pglogicalProven upgrade path, works across major version gaps
Multi-master with conflict resolutionpglogicalBuilt-in conflict strategies; native has no auto-resolution
Existing pglogical deployment, PG 15+Evaluate migrationNative now covers most features; migration reduces dependencies
Managed service without pglogical (Neon, Supabase)Nativepglogical is not available; native is the only option
Replicating from PG 9.xpglogicalNative logical replication does not exist before PG 10
High-availability with failoverNative (PG 17+)sync_replication_slots keeps logical slots on standbys
Selective replication to analytics DBEitherBoth support row/column filtering on PG 15+; native is simpler if no conflict resolution needed

The migration itself — allow me to walk you through it

If you have decided to migrate, I am pleased to report that the process is straightforward and can be done without downtime. The key insight — and it is a reassuring one — is that pglogical and native logical replication can run simultaneously. They use separate replication slots and do not conflict with each other. Two systems of service, operating in parallel, until you are satisfied the new arrangement is sound. I have guided this transition more than once, and it proceeds more smoothly than most teams expect.

Migration steps
-- Migrating from pglogical to native logical replication
-- Prerequisites: PostgreSQL 15+ on both sides, wal_level = logical

-- Step 1: Create a native publication matching your replication set
CREATE PUBLICATION app_pub
  FOR TABLE orders WHERE (status = 'completed'),
           TABLE customers,
           TABLE products;

-- Step 2: Create native subscription on the subscriber
-- (can coexist with pglogical temporarily)
CREATE SUBSCRIPTION app_sub
  CONNECTION 'host=primary port=5432 dbname=app'
  PUBLICATION app_pub
  WITH (copy_data = false);
  -- copy_data = false because pglogical already synced the data

-- Step 3: Verify both streams are in sync
SELECT * FROM pg_stat_subscription;

-- Step 4: Drop the pglogical subscription
SELECT pglogical.drop_subscription('old_pglogical_sub');

-- Step 5: Drop the pglogical extension when no longer needed
DROP EXTENSION pglogical;

A few practical notes, the sort that are easy to overlook in the planning and impossible to ignore in the execution:

  • Set copy_data = false on the native subscription. Since pglogical has already synchronized the data, you do not want native to copy it again. The native subscription will start streaming from the current WAL position.
  • Verify LSN alignment before dropping pglogical. Compare the received LSN on both the pglogical and native subscriptions to confirm they are receiving the same changes.
  • Translate your replication sets to publications. pglogical's replication sets map to native publications, but the syntax differs. Row filters in pglogical use row_filter on replication_set_add_table; native uses WHERE clauses on CREATE PUBLICATION.
  • Account for conflict resolution. If you rely on pglogical's conflict resolution, you need an alternative strategy before migrating. This might mean ensuring subscribers are strictly read-only, or implementing application-level conflict handling. Do not, I implore you, discover this gap on the day of the migration. That is not a learning experience. It is an incident.

After the migration, you will want to keep a close eye on the native subscription. Trust, but verify — particularly in the first week:

Monitoring native replication
-- Check native replication status and lag
SELECT
  subname,
  pid,
  received_lsn,
  latest_end_lsn,
  latest_end_time
FROM pg_stat_subscription;

-- Check publication tables and filters
SELECT * FROM pg_publication_tables
WHERE pubname = 'app_pub';

A respectful word on pglogical's present circumstances

pglogical is in maintenance mode. EDB (which acquired 2ndQuadrant in 2020) continues to publish compatibility releases — version 2.4.6 added support for PostgreSQL 17 and 18 — but no new features are planned. The engineering effort that once built pglogical now goes into EDB Postgres Distributed, a commercial product that extends pglogical with full DDL replication, write leaders, and parallel apply.

I should be clear: this is not a crisis. It is a natural succession. pglogical is stable, battle-tested, and will continue to receive maintenance updates. But it does mean the feature trajectory favours native. Every PostgreSQL release adds capabilities to native logical replication. pglogical's feature set is frozen. Over time, the balance tilts further toward native — not because pglogical has gotten worse, but because native keeps getting better. The senior member of staff has not declined. The household has simply grown to require things that only the newer systems provide.

For teams evaluating logical replication today, the question is not "which is better" in the abstract. That way lies the sort of theological debate I prefer to leave to conference talks. The question is "which covers my requirements with the least operational overhead." Increasingly, the answer is native. But not always. And a butler who cannot tell the difference between "increasingly" and "always" is a butler who has stopped paying attention.

Before you replicate — a question worth asking first

If you will indulge me a moment longer, I should like to raise a possibility that the comparison itself tends to obscure. A common reason teams reach for logical replication is to offload expensive analytical queries from the primary. The pattern is familiar: create a read replica, route reporting traffic there, keep the primary free for transactional workloads. It works. It also introduces synchronization lag, operational complexity, and the cost of a second database instance. Two households to manage where one existed before.

An alternative worth considering: if the expensive queries are aggregations, dashboards, or reporting queries that can tolerate data that is minutes (rather than milliseconds) stale, materialized views on the primary can serve the same purpose. A well-maintained materialized view caches the result of an expensive query and serves it at index-scan speed. No second database. No replication lag to monitor. No failover topology to maintain. One household, well-organized, doing the work of two.

Gold Lapel automates this pattern — it detects expensive query patterns in production traffic and creates materialized views automatically, refreshing them on a schedule tuned to the data's change rate. For workloads where read replicas exist primarily to handle reporting queries, this can eliminate the need for logical replication entirely.

I should be honest about the boundaries. This does not replace logical replication for all use cases — a butler who overstates his case is no butler at all. If you need a writable copy of data in another region, or filtered data feeds to downstream systems, or zero-downtime major version upgrades — you need replication. Full stop. But if your read replica exists because SELECT statements were overwhelming the primary, there may be a simpler path. And simpler, in infrastructure, is nearly always better.

Frequently asked questions