v2-0001-Allow-to-create-logical-slots-with-no-WAL-reserva.patch
application/octet-stream
Filename: v2-0001-Allow-to-create-logical-slots-with-no-WAL-reserva.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0001
Subject: Allow to create logical slots with no WAL reservation.
| File | + | − |
|---|---|---|
| contrib/test_decoding/expected/slot.out | 32 | 0 |
| contrib/test_decoding/sql/slot.sql | 12 | 0 |
| src/backend/catalog/system_functions.sql | 1 | 0 |
| src/backend/replication/logical/logical.c | 34 | 0 |
| src/backend/replication/logical/logicalfuncs.c | 8 | 8 |
| src/backend/replication/slotfuncs.c | 52 | 23 |
| src/backend/replication/walsender.c | 6 | 6 |
| src/include/catalog/pg_proc.dat | 4 | 4 |
| src/include/replication/logical.h | 7 | 0 |
From 67d28785b9d0a4cf10a863cc7d1265d5753a41a0 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Tue, 3 Jun 2025 15:31:47 -0700
Subject: [PATCH v2 1/2] Allow to create logical slots with no WAL reservation.
Author:
Reviewed-by:
Discussion: https://postgr.es/m/
---
contrib/test_decoding/expected/slot.out | 32 ++++++++
contrib/test_decoding/sql/slot.sql | 12 +++
src/backend/catalog/system_functions.sql | 1 +
src/backend/replication/logical/logical.c | 34 +++++++++
.../replication/logical/logicalfuncs.c | 16 ++--
src/backend/replication/slotfuncs.c | 75 +++++++++++++------
src/backend/replication/walsender.c | 12 +--
src/include/catalog/pg_proc.dat | 8 +-
src/include/replication/logical.h | 7 ++
9 files changed, 156 insertions(+), 41 deletions(-)
diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 7de03c79f6f..4475260286e 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -466,3 +466,35 @@ SELECT pg_drop_replication_slot('physical_slot');
(1 row)
+-- Test logical slots with not WAL reservation.
+SELECT 'init' FROM pg_create_logical_replication_slot('slot_nowal', 'test_decoding', false, false, false, false);
+ ?column?
+----------
+ init
+(1 row)
+
+-- No WAL reserved.
+SELECT restart_lsn, confirmed_flush_lsn FROM pg_replication_slots WHERE slot_name = 'slot_nowal';
+ restart_lsn | confirmed_flush_lsn
+-------------+---------------------
+ |
+(1 row)
+
+-- Use this slot and check the WAL reservation again.
+SELECT data FROM pg_logical_slot_get_changes('slot_nowal', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+ data
+------
+(0 rows)
+
+SELECT restart_lsn is not null as valid_restart_lsn, confirmed_flush_lsn is not null as valid_confirmed_flush_lsn FROM pg_replication_slots WHERE slot_name = 'slot_nowal';
+ valid_restart_lsn | valid_confirmed_flush_lsn
+-------------------+---------------------------
+ t | t
+(1 row)
+
+SELECT pg_drop_replication_slot('slot_nowal');
+ pg_drop_replication_slot
+--------------------------
+
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 580e3ae3bef..3da5d6eab9b 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -190,3 +190,15 @@ SELECT pg_drop_replication_slot('failover_true_slot');
SELECT pg_drop_replication_slot('failover_false_slot');
SELECT pg_drop_replication_slot('failover_default_slot');
SELECT pg_drop_replication_slot('physical_slot');
+
+-- Test logical slots with not WAL reservation.
+SELECT 'init' FROM pg_create_logical_replication_slot('slot_nowal', 'test_decoding', false, false, false, false);
+
+-- No WAL reserved.
+SELECT restart_lsn, confirmed_flush_lsn FROM pg_replication_slots WHERE slot_name = 'slot_nowal';
+
+-- Use this slot and check the WAL reservation again.
+SELECT data FROM pg_logical_slot_get_changes('slot_nowal', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+SELECT restart_lsn is not null as valid_restart_lsn, confirmed_flush_lsn is not null as valid_confirmed_flush_lsn FROM pg_replication_slots WHERE slot_name = 'slot_nowal';
+
+SELECT pg_drop_replication_slot('slot_nowal');
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 566f308e443..5a801f5f3bb 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -480,6 +480,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
IN temporary boolean DEFAULT false,
IN twophase boolean DEFAULT false,
IN failover boolean DEFAULT false,
+ IN immediately_reserve boolean DEFAULT true,
OUT slot_name name, OUT lsn pg_lsn)
RETURNS RECORD
LANGUAGE INTERNAL
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 1d56d0c4ef3..2a34d2bf846 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -303,6 +303,40 @@ StartupDecodingContext(List *output_plugin_options,
return ctx;
}
+/*
+ * Create the logical decoding and initialize it if necessary. This function
+ * can be used for logical slot that might not have been initialized yet.
+ */
+LogicalDecodingContext *
+CreateOrInitDecodingContext(XLogRecPtr restart_lsn,
+ List *output_plugin_options,
+ bool fast_foward,
+ XLogReaderRoutine *xl_routine,
+ LogicalOutputPluginWriterPrepareWrite prepare_write,
+ LogicalOutputPluginWriterWrite do_write,
+ LogicalOutputPluginWriterUpdateProgress update_progress)
+{
+ LogicalDecodingContext *ctx;
+
+ /* Initialize the slot with a new logical decoding if not yet */
+ if (XLogRecPtrIsInvalid(MyReplicationSlot->data.restart_lsn))
+ {
+ ctx = CreateInitDecodingContext(NameStr(MyReplicationSlot->data.plugin),
+ output_plugin_options, false,
+ restart_lsn, xl_routine,
+ prepare_write, do_write, update_progress);
+
+ DecodingContextFindStartpoint(ctx);
+
+ FreeDecodingContext(ctx);
+ }
+
+ ctx = CreateDecodingContext(restart_lsn, output_plugin_options, fast_foward,
+ xl_routine, prepare_write, do_write, update_progress);
+
+ return ctx;
+}
+
/*
* Create a new decoding context, for a new logical slot.
*
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index ca53caac2f2..8469280a37b 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -202,14 +202,14 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
PG_TRY();
{
/* restart at slot's confirmed_flush */
- ctx = CreateDecodingContext(InvalidXLogRecPtr,
- options,
- false,
- XL_ROUTINE(.page_read = read_local_xlog_page,
- .segment_open = wal_segment_open,
- .segment_close = wal_segment_close),
- LogicalOutputPrepareWrite,
- LogicalOutputWrite, NULL);
+ ctx = CreateOrInitDecodingContext(InvalidXLogRecPtr,
+ options,
+ false,
+ XL_ROUTINE(.page_read = read_local_xlog_page,
+ .segment_open = wal_segment_open,
+ .segment_close = wal_segment_close),
+ LogicalOutputPrepareWrite,
+ LogicalOutputWrite, NULL);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 36cc2ed4e44..ccbc3732e95 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -116,13 +116,14 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
static void
create_logical_replication_slot(char *name, char *plugin,
bool temporary, bool two_phase,
- bool failover,
+ bool failover, bool immediately_reserve,
XLogRecPtr restart_lsn,
bool find_startpoint)
{
LogicalDecodingContext *ctx = NULL;
Assert(!MyReplicationSlot);
+ Assert(plugin != NULL);
/*
* Acquire a logical decoding slot, this will check for conflicting names.
@@ -136,30 +137,55 @@ create_logical_replication_slot(char *name, char *plugin,
temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
failover, false);
- /*
- * Create logical decoding context to find start point or, if we don't
- * need it, to 1) bump slot's restart_lsn and xmin 2) check plugin sanity.
- *
- * Note: when !find_startpoint this is still important, because it's at
- * this point that the output plugin is validated.
- */
- ctx = CreateInitDecodingContext(plugin, NIL,
- false, /* just catalogs is OK */
- restart_lsn,
- XL_ROUTINE(.page_read = read_local_xlog_page,
- .segment_open = wal_segment_open,
- .segment_close = wal_segment_close),
- NULL, NULL, NULL);
+ if (immediately_reserve)
+ {
+ /*
+ * Create logical decoding context to find start point or, if we don't
+ * need it, to 1) bump slot's restart_lsn and xmin 2) check plugin
+ * sanity.
+ *
+ * Note: when !find_startpoint this is still important, because it's
+ * at this point that the output plugin is validated.
+ */
+ ctx = CreateInitDecodingContext(plugin, NIL,
+ false, /* just catalogs is OK */
+ restart_lsn,
+ XL_ROUTINE(.page_read = read_local_xlog_page,
+ .segment_open = wal_segment_open,
+ .segment_close = wal_segment_close),
+ NULL, NULL, NULL);
- /*
- * If caller needs us to determine the decoding start point, do so now.
- * This might take a while.
- */
- if (find_startpoint)
- DecodingContextFindStartpoint(ctx);
+ /*
+ * If caller needs us to determine the decoding start point, do so
+ * now. This might take a while.
+ */
+ if (find_startpoint)
+ DecodingContextFindStartpoint(ctx);
+
+ /* don't need the decoding context anymore */
+ FreeDecodingContext(ctx);
+ }
+ else
+ {
+ NameData plugin_name;
+
+ /*
+ * On a standby, this check is also required while creating the slot.
+ * Check the comments in the function.
+ */
+ CheckLogicalDecodingRequirements();
- /* don't need the decoding context anymore */
- FreeDecodingContext(ctx);
+ /*
+ * Register output plugin name with slot. We need the mutex to avoid
+ * concurrent reading of a partially copied string. But we don't want
+ * any complicated code while holding a spinlock, so do namestrcpy()
+ * outside.
+ */
+ namestrcpy(&plugin_name, plugin);
+ SpinLockAcquire(&MyReplicationSlot->mutex);
+ MyReplicationSlot->data.plugin = plugin_name;
+ SpinLockRelease(&MyReplicationSlot->mutex);
+ }
}
/*
@@ -173,6 +199,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
bool temporary = PG_GETARG_BOOL(2);
bool two_phase = PG_GETARG_BOOL(3);
bool failover = PG_GETARG_BOOL(4);
+ bool immediately_reserve = PG_GETARG_BOOL(5);
Datum result;
TupleDesc tupdesc;
HeapTuple tuple;
@@ -191,6 +218,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
temporary,
two_phase,
failover,
+ immediately_reserve,
InvalidXLogRecPtr,
true);
@@ -726,6 +754,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
temporary,
false,
false,
+ true,
src_restart_lsn,
false);
}
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 9fa8beb6103..85668ef807c 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1462,12 +1462,12 @@ StartLogicalReplication(StartReplicationCmd *cmd)
* are reported early.
*/
logical_decoding_ctx =
- CreateDecodingContext(cmd->startpoint, cmd->options, false,
- XL_ROUTINE(.page_read = logical_read_xlog_page,
- .segment_open = WalSndSegmentOpen,
- .segment_close = wal_segment_close),
- WalSndPrepareWrite, WalSndWriteData,
- WalSndUpdateProgress);
+ CreateOrInitDecodingContext(cmd->startpoint, cmd->options, false,
+ XL_ROUTINE(.page_read = logical_read_xlog_page,
+ .segment_open = WalSndSegmentOpen,
+ .segment_close = wal_segment_close),
+ WalSndPrepareWrite, WalSndWriteData,
+ WalSndUpdateProgress);
xlogreader = logical_decoding_ctx->reader;
WalSndSetState(WALSNDSTATE_CATCHUP);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 37a484147a8..62b302fbd1f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11470,10 +11470,10 @@
{ oid => '3786', descr => 'set up a logical replication slot',
proname => 'pg_create_logical_replication_slot', provolatile => 'v',
proparallel => 'u', prorettype => 'record',
- proargtypes => 'name name bool bool bool',
- proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
- proargmodes => '{i,i,i,i,i,o,o}',
- proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
+ proargtypes => 'name name bool bool bool bool',
+ proallargtypes => '{name,name,bool,bool,bool,bool,name,pg_lsn}',
+ proargmodes => '{i,i,i,i,i,i,o,o}',
+ proargnames => '{slot_name,plugin,temporary,twophase,failover,immediately_reserve,slot_name,lsn}',
prosrc => 'pg_create_logical_replication_slot' },
{ oid => '4222',
descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/replication/logical.h b/src/include/replication/logical.h
index 2e562bee5a9..481ea53e7e1 100644
--- a/src/include/replication/logical.h
+++ b/src/include/replication/logical.h
@@ -132,6 +132,13 @@ extern LogicalDecodingContext *CreateDecodingContext(XLogRecPtr start_lsn,
LogicalOutputPluginWriterPrepareWrite prepare_write,
LogicalOutputPluginWriterWrite do_write,
LogicalOutputPluginWriterUpdateProgress update_progress);
+extern LogicalDecodingContext *CreateOrInitDecodingContext(XLogRecPtr restart_lsn,
+ List *output_plugin_options,
+ bool fast_foward,
+ XLogReaderRoutine *xl_routine,
+ LogicalOutputPluginWriterPrepareWrite prepare_write,
+ LogicalOutputPluginWriterWrite do_write,
+ LogicalOutputPluginWriterUpdateProgress update_progress);
extern void DecodingContextFindStartpoint(LogicalDecodingContext *ctx);
extern bool DecodingContextReady(LogicalDecodingContext *ctx);
extern void FreeDecodingContext(LogicalDecodingContext *ctx);
--
2.43.5