You can create local logical replication slots on read-only instances so that downstream subscribers consume changes directly from a read-only instance, offloading the logical replication load from the primary instance.
Prerequisites
Your instance must run PostgreSQL 16 or later with a minor kernel version of 20260530 or later. If your instance does not support this feature, upgrade the minor kernel version. For more information, see Upgrade the minor kernel version.
Background
In native PostgreSQL, logical replication slots exist only on the primary instance, so downstream subscribers must connect to the primary instance to consume changes. RDS PostgreSQL extends this by supporting local logical replication slots on read-only instances, allowing subscribers to connect to a read-only instance instead and offload the replication workload from the primary instance. For more information about logical replication slots, see the official PostgreSQL documentation.
Usage notes
-
A logical replication slot created on a read-only instance is a local slot. It is not included in the failover process. If the read-only instance is rebuilt, the slot is lost and you must recreate it.
-
Use this feature only on read-only instances that are not critical for high availability. If a read-only instance handles high-availability read traffic, the loss of a slot can interrupt downstream subscriptions. Evaluate the potential business impact.
Usage examples
The following examples use a primary instance (RW) and a read-only instance (RO) synchronized through streaming replication, covering three typical use cases:
-
Basic usage: Manually create and consume a slot on a read-only instance using the
test_decodingplugin. This method is suitable for scenarios that require custom decoding logic. -
Subscribe to a read-only instance (automatic slot creation): This is the most common use case, where the
CREATE SUBSCRIPTIONcommand automatically creates a slot on the read-only instance. -
Subscribe to a read-only instance (manual slot pre-creation): This is an advanced use case where you manually create a slot before establishing a subscription. This ensures that no changes are lost during the setup process.
Basic usage: Manual creation and consumption
Manually create a logical replication slot on a read-only instance and consume data changes from the primary instance using the test_decoding plugin.
-
Create a local logical replication slot on the read-only instance.
-- [RO] Create a local logical replication slot. SELECT pg_create_logical_replication_slot('my_slot', 'test_decoding'); -
Verify that the slot was created.
-- [RO] Verify that the slot was created. SELECT slot_name, plugin FROM pg_replication_slots WHERE slot_name = 'my_slot';Sample output:
slot_name | plugin -----------+--------------- my_slot | test_decoding (1 row) -
Write data to the primary instance.
-- [RW] Write data to the primary instance. INSERT INTO my_table VALUES (1), (2), (3); -
Wait for the WAL to be replayed on the read-only instance.
On the primary instance, query the current WAL LSN:
-- [RW] Get the current WAL LSN on the primary instance. SELECT pg_current_wal_lsn();On the read-only instance, query the replay progress until the replay LSN is greater than or equal to the WAL LSN of the primary instance:
-- [RO] Check the replay progress on the read-only instance. SELECT pg_last_wal_replay_lsn();NoteIn PostgreSQL 15 and later, you can also use the
pg_wal_replay_wait(target_lsn)function to wait for the replay to reach a specific LSN instead of polling. -
Consume the changes through the slot on the read-only instance.
-- [RO] Consume changes through the slot. SELECT * FROM pg_logical_slot_get_changes('my_slot', NULL, NULL);Sample output:
lsn | xid | data -----------+-----+---------------------------------------- 0/XXXXXXX | 123 | table public.my_table: INSERT: ... (3 rows) -
When the slot is no longer needed, drop it.
-- [RO] Drop the slot. SELECT pg_drop_replication_slot('my_slot');
Subscription with automatic slot creation
In this common use case, a downstream subscriber connects directly to the read-only instance, and the CREATE SUBSCRIPTION command automatically creates a slot on that instance.
-
Create a table and a publication on the primary instance.
-- [RW] Create a table and a publication on the primary instance. CREATE TABLE tab_rep (id int PRIMARY KEY, data text); CREATE PUBLICATION my_pub FOR TABLE tab_rep; -
Wait for the WAL to be replayed on the read-only instance to ensure that the publication is visible. For instructions, see Step 4 in the Basic usage section.
-
Create the same table structure on the downstream subscriber.
-- [Subscriber] Create the table structure on the subscriber instance. CREATE TABLE tab_rep (id int PRIMARY KEY, data text); -
On the downstream subscriber, create a subscription that connects to the read-only instance.
-- [Subscriber] Create a subscription that connects to the read-only instance. -- The subscription automatically creates a slot named 'my_sub' on the read-only instance. CREATE SUBSCRIPTION my_sub CONNECTION 'host=<RO_HOST> port=<RO_PORT> dbname=postgres' PUBLICATION my_pub WITH (copy_data = off);NoteThis action automatically creates a logical replication slot on the read-only instance. This slot is local to the read-only instance, is not included in failover, and is lost if the instance is rebuilt.
-
Write data to the primary instance and verify the synchronization.
-- [RW] Write data to the primary instance. INSERT INTO tab_rep VALUES (1, 'hello'), (2, 'world');Data flow: primary instance → (streaming replication/WAL) → read-only instance → (logical replication) → downstream subscriber.
-- [Subscriber] Verify that the data is synchronized. SELECT * FROM tab_rep;Sample output:
id | data ----+------- 1 | hello 2 | world (2 rows) -
When the subscription is no longer needed, drop it on the downstream subscriber. This action automatically removes the slot from the read-only instance.
-- [Subscriber] Drop the subscription. This automatically removes the slot on the read-only instance. DROP SUBSCRIPTION my_sub;
Subscription with manual slot pre-creation
To ensure no changes are lost during setup, you can manually pre-create the slot before establishing a subscription.
-
Manually create a slot on the read-only instance.
-- [RO] Manually create the slot on the read-only instance. SELECT pg_create_logical_replication_slot('my_sub', 'pgoutput'); -
On the downstream subscriber, create a subscription that uses the existing slot.
-- [Subscriber] Create a subscription and use the existing slot. CREATE SUBSCRIPTION my_sub CONNECTION 'host=<RO_HOST> port=<RO_PORT> dbname=postgres' PUBLICATION my_pub WITH (copy_data = off, create_slot = false);NoteSetting
create_slot = falseinstructs theCREATE SUBSCRIPTIONcommand to use the existing slot on the read-only instance instead of creating a new one. The subsequent steps are the same as in the Subscribe to a read-only instance (automatic slot creation) section.